mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-07 16:09:27 +02:00
Merge branch 'beta' into rest
This commit is contained in:
commit
adeedb84d5
@ -2,7 +2,8 @@
|
||||
|
||||
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.
|
||||
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 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
|
||||
|
||||
@ -11,7 +12,6 @@ Please make sure you understand everything relevant to your changes from the [Ta
|
||||
- [Getting Started](#-getting-started)
|
||||
- [Documentation](#-documentation)
|
||||
- [Testing Your Changes](#-testing-your-changes)
|
||||
- [Localization](#-localization)
|
||||
- [Development Save File (Unlock Everything)](#-development-save-file)
|
||||
|
||||
## 🛠️ Development Basics
|
||||
@ -26,17 +26,14 @@ If you have the motivation and experience with Typescript/Javascript (or are wil
|
||||
|
||||
- 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/)
|
||||
- The repository [forked](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) and [cloned](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) locally on your device
|
||||
|
||||
### Running Locally
|
||||
|
||||
1. Clone the repo and in the root directory run `pnpm install`
|
||||
1. Run `pnpm install` from the repository root
|
||||
- *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/)).
|
||||
@ -60,8 +57,13 @@ You are free to comment on any issue so that you may be assigned to it and we ca
|
||||
## 📚 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.
|
||||
|
||||
Additionally, the [docs folder](./docs) contains a variety of in-depth documents and guides useful for aspiring contributors.
|
||||
Notable topics include:
|
||||
- [Commenting your code](./docs/comments.md)
|
||||
- [Linting & Formatting](./docs/linting.md)
|
||||
- [Localization](./docs/localization.md)
|
||||
- [Enemy AI move selection](./docs/enemy-ai.md)
|
||||
|
||||
Again, if you have unanswered questions please feel free to ask!
|
||||
|
||||
@ -101,62 +103,6 @@ Most non-trivial changes (*especially bug fixes*) should come along with new tes
|
||||
- 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).
|
||||
|
||||
|
@ -70,7 +70,10 @@
|
||||
},
|
||||
"style": {
|
||||
"useEnumInitializers": "off", // large enums like Moves/Species would make this cumbersome
|
||||
"useBlockStatements": "error",
|
||||
"useBlockStatements": {
|
||||
"level": "error",
|
||||
"fix": "safe"
|
||||
},
|
||||
"useConst": "error",
|
||||
"useImportType": "error",
|
||||
"noNonNullAssertion": "off", // TODO: Turn this on ASAP and fix all non-null assertions in non-test files
|
||||
|
@ -1,13 +0,0 @@
|
||||
import { Graphviz } from "@hpcc-js/wasm/graphviz";
|
||||
|
||||
const graphviz = await Graphviz.load();
|
||||
|
||||
const inputFile = [];
|
||||
for await (const chunk of process.stdin) {
|
||||
inputFile.push(chunk);
|
||||
}
|
||||
|
||||
const file = Buffer.concat(inputFile).toString("utf-8");
|
||||
|
||||
const svg = graphviz.dot(file, "svg");
|
||||
process.stdout.write(svg);
|
142
docs/localization.md
Normal file
142
docs/localization.md
Normal file
@ -0,0 +1,142 @@
|
||||
# Localization 101
|
||||
|
||||
PokéRogue's localization team puts immense effort into making the game accessible around the world, supporting over 12 different languages at the time of writing this document.
|
||||
As a developer, it's important to help maintain global accessibility by effectively coordinating with the Translation Team on any new features or enhancements.
|
||||
|
||||
This document aims to cover everything you need to know to help keep the integration process for localization smooth and simple.
|
||||
|
||||
# Prerequisites
|
||||
Before you continue, this document assumes:
|
||||
- You have already forked the repository and set up a development environment according to [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
- You have a basic level of familiarity with Git commands and GitHub repositories.
|
||||
- You have joined the [community Discord](https://discord.gg/pokerogue) and have access to `#dev-corner` and related channels via **[#select-roles](https://discord.com/channels/1125469663833370665/1194825607738052621)**.
|
||||
This is the easiest way to keep in touch with both the Translation Team and other like-minded contributors!
|
||||
|
||||
# About the `pokerogue-locales` submodule
|
||||
|
||||
PokéRogue's translations are managed under a separate dedicated repository, [`pokerogue-locales`](https://github.com/pagefaultgames/pokerogue-locales/).
|
||||
This repository is integrated into the main one as a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) within the `public/locales` folder.
|
||||
|
||||
## What Is a Submodule?
|
||||
|
||||
In essence, a submodule is a way for one repository (i.e. `pokerogue`) to use another repository (i.e. `pokerogue-locales`) internally.
|
||||
The parent repo (the "superproject") houses a cloned version of the 2nd repository (the "submodule") inside it, making locales effectively a "repository within a repository", so to speak.
|
||||
|
||||
>[!TIP]
|
||||
> Many popular IDEs have integrated `git` support with special handling around submodules:
|
||||
> 
|
||||
>
|
||||
> 
|
||||
|
||||
## Fetching Changes from Submodules
|
||||
|
||||
The following command will initialize your branch's locales repository and update its HEAD:
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> This command is run _automatically_ after cloning, merging or changing branches, so you should rarely have to run it manually.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you run into issues with the `locales` submodule, try deleting the `.git/modules/public` and `public/locales` folders before re-running the command.
|
||||
|
||||
## How Are Translations Integrated?
|
||||
|
||||
This project uses the [i18next library](https://www.i18next.com/) to integrate translations from `public/locales` into the source code.
|
||||
The basic process for fetching translated text goes roughly as follows:
|
||||
1. The source code fetches text by a given key.
|
||||
```ts
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("fileName:keyName", { arg1: "Hello", arg2: "an example", ... })
|
||||
);
|
||||
```
|
||||
2. The game looks up the key in the corresponding JSON file for the user's language.
|
||||
```jsonc
|
||||
// from "en/file-name.json"...
|
||||
{
|
||||
"keyName": "{{arg1}}! This is {{arg2}} of translated text!"
|
||||
}
|
||||
```
|
||||
If the key doesn't exist for the given language, the game will default to an appropriate fallback (usually the corresponding English key).
|
||||
3. The game shows the translated text to the user.
|
||||
```ts
|
||||
"Hello! This is an example of translated text!"
|
||||
```
|
||||
|
||||
# Submitting Locales Changes
|
||||
If you have a feature or enhancement that requires additions or changes to in-game text, you will need to make a fork of the `pokerogue-locales` repo and submit your text changes as a pull request _in addition_ to your pull request to the main project.
|
||||
Since these two PRs aren't _technically_ linked, it's important to coordinate with the Translation Team to ensure that both PRs are integrated safely into the project.
|
||||
|
||||
> [!CAUTION]
|
||||
> **DO NOT HARDCODE PLAYER-FACING TEXT INTO THE CODE!**
|
||||
|
||||
## Making Changes
|
||||
|
||||
One perk of submodules is you don't actually _need_ to clone the locales repository to start contributing - initializing the submodule already does that for you.
|
||||
|
||||
Given `pokerogue-locales` is a full-fledged `git` repository _inside_ `pokerogue`, making changes is roughly the same as normal, merely using `public/locales` as your root directory.
|
||||
|
||||
> [!WARNING]
|
||||
> Make sure to checkout or rebase onto `upstream/HEAD` **BEFORE** creating a PR!
|
||||
> The checked-out commit is based on the superproject's SHA-1 by default, so hastily making changes may see you basing your commits on last week's `HEAD`.
|
||||
|
||||
## Requirements for Adding Translated Text
|
||||
When your new feature or enhancement requires adding a new locales key **without changing text in existing keys**, we require the following workflow with regards to localization:
|
||||
1. You (the developer) 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 locales repository.
|
||||
2. You then make another pull request — this time to the `pokerogue-locales` repository — adding a new entry with text for each key you added to your main PR.
|
||||
- You must add the corresponding **English keys** while making the PR; the Translation Team can take care of the rest[^2].
|
||||
- For any feature pulled from the mainline Pokémon games (e.g. a Move or Ability implementation), it's best practice to include a source link for any added text.
|
||||
[Poké Corpus](https://abcboy101.github.io/poke-corpus/) is a great resource for finding text from the mainline games; otherwise, a video/picture showing the text being displayed should suffice.
|
||||
- You should also [notify the current Head of Translation](#notifying-translation) to ensure a fast response.
|
||||
3. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes).
|
||||
4. The Translation Team will approve the locale PR (after corrections, if necessary), then merge it into `pokerogue-locales`.
|
||||
5. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment.
|
||||
|
||||
[^2]: For those wondering, the reason for choosing English specifically is due to it being the master language set in Pontoon (the program used by the Translation Team to perform locale updates).
|
||||
If a key is present in any language _except_ the master language, it won't appear anywhere else in the translation tool, rendering missing English keys quite a hassle.
|
||||
|
||||
### Requirements for Modifying Translated Text
|
||||
|
||||
PRs that modify existing text have different risks with respect to coordination between development and translation, so their requirements are slightly different:
|
||||
- As above, you set up 2 PRs: one for the feature itself in the main repo, and another for the associated locales changes in the locale repo.
|
||||
- Now, however, you need to have your main PR be approved by the Dev Team **before** your corresponding locale changes are merged in.
|
||||
- After your main PR is approved, the Translation Team will merge your locale PR, and you may update the submodule and post video evidence of integration into the **locales PR**.
|
||||
- A Lead or Senior Translator from the Translation Team will then approve your main PR (if all is well), clearing your feature for merging into `beta`.
|
||||
|
||||
## Documenting Locales Changes
|
||||
|
||||
After making a PR involving any outwards-facing behavior (but _especially_ locales-related ones), it's generally considered good practice to attach proof of those changes working in-game.
|
||||
|
||||
The basic procedure is roughly as follows:
|
||||
1. Update your locales submodule to point to **the branch you used to make the locales PR**. \
|
||||
Many IDEs with `git` integration support doing this from the GUI, \
|
||||
or you can simply do it via command-line:
|
||||
```bash
|
||||
cd public/locales
|
||||
git checkout your-branch-name-here
|
||||
```
|
||||
2. Set some of the [in-game overrides](../CONTRIBUTING.md#1---manual-testing) inside `overrides.ts` to values corresponding to the interactions being tested.
|
||||
3. Start a local dev server (`pnpm start:dev`) and open localhost in your browser.
|
||||
4. Take screenshots or record a video of the locales changes being displayed in-game using the software of your choice[^2].
|
||||
|
||||
[^2]: For those lacking a screen capture device, [OBS Studio](https://obsproject.com) is a popular open-source option.
|
||||
|
||||
> [!NOTE]
|
||||
> For those aiming to film their changes, bear in mind that GitHub has a hard **10mB limit** on uploaded media content.
|
||||
> If your video is too large, consider making it shorter or downscaling the quality.
|
||||
|
||||
## Notifying Translation
|
||||
Put simply, stating that a PR exists makes it much easier to review and merge.
|
||||
|
||||
The easiest way to do this is by **pinging the current Head of Translation** in the [community Discord](https://discord.gg/pokerogue) (ideally in `#dev-corner` or similar).
|
||||
|
||||
<!-- Remember to update this everytime the head of translation changes! -->
|
||||
> [!IMPORTANT]
|
||||
> The current Head of Translation is: \
|
||||
> ** @lugiadrien **
|
||||
|
||||
# Closing Remarks
|
||||
If you have any questions about the developer process for localization, don't hesitate to ask!
|
||||
Feel free to contact us on Discord - the Dev Team and Translation Team will be happy to answer any questions.
|
@ -21,15 +21,13 @@
|
||||
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched",
|
||||
"docs": "typedoc",
|
||||
"depcruise": "depcruise src test",
|
||||
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
|
||||
"postinstall": "lefthook install && lefthook run post-merge",
|
||||
"postinstall": "lefthook install; git submodule update --init --recursive",
|
||||
"update-version:patch": "pnpm version patch --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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.0",
|
||||
"@hpcc-js/wasm": "^2.22.4",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^22.13.14",
|
||||
"@vitest/coverage-istanbul": "^3.0.9",
|
||||
|
@ -45,9 +45,6 @@ importers:
|
||||
'@biomejs/biome':
|
||||
specifier: 2.0.0
|
||||
version: 2.0.0
|
||||
'@hpcc-js/wasm':
|
||||
specifier: ^2.22.4
|
||||
version: 2.22.4
|
||||
'@types/jsdom':
|
||||
specifier: ^21.1.7
|
||||
version: 21.1.7
|
||||
@ -436,10 +433,6 @@ packages:
|
||||
'@gerrit0/mini-shiki@3.2.2':
|
||||
resolution: {integrity: sha512-vaZNGhGLKMY14HbF53xxHNgFO9Wz+t5lTlGNpl2N9xFiKQ0I5oIe0vKjU9dh7Nb3Dw6lZ7wqUE0ri+zcdpnK+Q==}
|
||||
|
||||
'@hpcc-js/wasm@2.22.4':
|
||||
resolution: {integrity: sha512-58JkRkxZffiBAbZhc7z+9iaaAOmn1cyxLL3rRwsUvco/I0Wwb7uVAlHM9HiU6XASe2k11jrIjCFff1t9QKjlqg==}
|
||||
hasBin: true
|
||||
|
||||
'@inquirer/checkbox@4.1.4':
|
||||
resolution: {integrity: sha512-d30576EZdApjAMceijXA5jDzRQHT/MygbC+J8I7EqA6f/FRpYxlRtRJbHF8gHeWYeSdOuTEJqonn7QLB1ELezA==}
|
||||
engines: {node: '>=18'}
|
||||
@ -2332,10 +2325,6 @@ snapshots:
|
||||
'@shikijs/types': 3.2.1
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
|
||||
'@hpcc-js/wasm@2.22.4':
|
||||
dependencies:
|
||||
yargs: 17.7.2
|
||||
|
||||
'@inquirer/checkbox@4.1.4(@types/node@22.13.14)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.1.9(@types/node@22.13.14)
|
||||
|
6
pnpm-workspace.yaml
Normal file
6
pnpm-workspace.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
- msw
|
||||
- lefthook
|
||||
|
||||
shellEmulator: true
|
Binary file not shown.
BIN
public/audio/bgm/battle_legendary_eternatus_p1.mp3
Normal file
BIN
public/audio/bgm/battle_legendary_eternatus_p1.mp3
Normal file
Binary file not shown.
BIN
public/audio/bgm/battle_legendary_eternatus_p2.mp3
Normal file
BIN
public/audio/bgm/battle_legendary_eternatus_p2.mp3
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/fonts/pokemon-bw.ttf
Normal file
BIN
public/fonts/pokemon-bw.ttf
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
BIN
public/images/items/berry_juice_bad.png
Normal file
BIN
public/images/items/berry_juice_bad.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 785 B |
Before Width: | Height: | Size: 327 B After Width: | Height: | Size: 327 B |
@ -1 +1 @@
|
||||
Subproject commit fade123e20ff951e199d7c0466686fe8c5511643
|
||||
Subproject commit aa94b0b68265c26f728d154998582bb629f2b850
|
12
src/@types/attack-move-result.ts
Normal file
12
src/@types/attack-move-result.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import type { DamageResult } from "#app/@types/damage-result";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
|
||||
export interface AttackMoveResult {
|
||||
move: MoveId;
|
||||
result: DamageResult;
|
||||
damage: number;
|
||||
critical: boolean;
|
||||
sourceId: number;
|
||||
sourceBattlerIndex: BattlerIndex;
|
||||
}
|
21
src/@types/damage-result.ts
Normal file
21
src/@types/damage-result.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import type { HitResult } from "#enums/hit-result";
|
||||
|
||||
/** Union type containing all damage-dealing {@linkcode HitResult}s. */
|
||||
export type DamageResult =
|
||||
| HitResult.EFFECTIVE
|
||||
| HitResult.SUPER_EFFECTIVE
|
||||
| HitResult.NOT_VERY_EFFECTIVE
|
||||
| HitResult.ONE_HIT_KO
|
||||
| HitResult.CONFUSION
|
||||
| HitResult.INDIRECT_KO
|
||||
| HitResult.INDIRECT;
|
||||
|
||||
/** Interface containing the results of a damage calculation for a given move. */
|
||||
export interface DamageCalculationResult {
|
||||
/** `true` if the move was cancelled (thus suppressing "No Effect" messages) */
|
||||
cancelled: boolean;
|
||||
/** The effectiveness of the move */
|
||||
result: HitResult;
|
||||
/** The damage dealt by the move */
|
||||
damage: number;
|
||||
}
|
41
src/@types/illusion-data.ts
Normal file
41
src/@types/illusion-data.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import type { Gender } from "#app/data/gender";
|
||||
import type PokemonSpecies from "#app/data/pokemon-species";
|
||||
import type { Variant } from "#app/sprites/variant";
|
||||
import type { PokeballType } from "#enums/pokeball";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
|
||||
/**
|
||||
* Data pertaining to a Pokemon's Illusion.
|
||||
*/
|
||||
export interface IllusionData {
|
||||
basePokemon: {
|
||||
/** The actual name of the Pokemon */
|
||||
name: string;
|
||||
/** The actual nickname of the Pokemon */
|
||||
nickname: string;
|
||||
/** Whether the base pokemon is shiny or not */
|
||||
shiny: boolean;
|
||||
/** The shiny variant of the base pokemon */
|
||||
variant: Variant;
|
||||
/** Whether the fusion species of the base pokemon is shiny or not */
|
||||
fusionShiny: boolean;
|
||||
/** The variant of the fusion species of the base pokemon */
|
||||
fusionVariant: Variant;
|
||||
};
|
||||
/** The species of the illusion */
|
||||
species: SpeciesId;
|
||||
/** The formIndex of the illusion */
|
||||
formIndex: number;
|
||||
/** The gender of the illusion */
|
||||
gender: Gender;
|
||||
/** The pokeball of the illusion */
|
||||
pokeball: PokeballType;
|
||||
/** The fusion species of the illusion if it's a fusion */
|
||||
fusionSpecies?: PokemonSpecies;
|
||||
/** The fusionFormIndex of the illusion */
|
||||
fusionFormIndex?: number;
|
||||
/** The fusionGender of the illusion if it's a fusion */
|
||||
fusionGender?: Gender;
|
||||
/** The level of the illusion (not used currently) */
|
||||
level?: number;
|
||||
}
|
13
src/@types/turn-move.ts
Normal file
13
src/@types/turn-move.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { MoveResult } from "#enums/move-result";
|
||||
import type { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
||||
/** A record of a move having been used. */
|
||||
export interface TurnMove {
|
||||
move: MoveId;
|
||||
targets: BattlerIndex[];
|
||||
useMode: MoveUseMode;
|
||||
result?: MoveResult;
|
||||
turn?: number;
|
||||
}
|
@ -18,6 +18,7 @@ import {
|
||||
isNullOrUndefined,
|
||||
BooleanHolder,
|
||||
type Constructor,
|
||||
isBetween,
|
||||
} from "#app/utils/common";
|
||||
import { deepMergeSpriteData } from "#app/utils/data";
|
||||
import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier";
|
||||
@ -164,10 +165,6 @@ import { PhaseManager } from "./phase-manager";
|
||||
|
||||
const DEBUG_RNG = false;
|
||||
|
||||
const OPP_IVS_OVERRIDE_VALIDATED: number[] = (
|
||||
Array.isArray(Overrides.OPP_IVS_OVERRIDE) ? Overrides.OPP_IVS_OVERRIDE : new Array(6).fill(Overrides.OPP_IVS_OVERRIDE)
|
||||
).map(iv => (Number.isNaN(iv) || iv === null || iv > 31 ? -1 : iv));
|
||||
|
||||
export interface PokeballCounts {
|
||||
[pb: string]: number;
|
||||
}
|
||||
@ -800,12 +797,14 @@ export default class BattleScene extends SceneBase {
|
||||
// TODO: Add `undefined` to return type
|
||||
/**
|
||||
* Returns an array of PlayerPokemon of length 1 or 2 depending on if in a double battle or not.
|
||||
* Does not actually check if the pokemon are on the field or not.
|
||||
* @param active - (Default `false`) Whether to consider only {@linkcode Pokemon.isActive | active} on-field pokemon
|
||||
* @returns array of {@linkcode PlayerPokemon}
|
||||
*/
|
||||
public getPlayerField(): PlayerPokemon[] {
|
||||
public getPlayerField(active = false): PlayerPokemon[] {
|
||||
const party = this.getPlayerParty();
|
||||
return party.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1));
|
||||
return party
|
||||
.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1))
|
||||
.filter(p => !active || p.isActive());
|
||||
}
|
||||
|
||||
public getEnemyParty(): EnemyPokemon[] {
|
||||
@ -934,9 +933,32 @@ export default class BattleScene extends SceneBase {
|
||||
nature,
|
||||
dataSource,
|
||||
);
|
||||
|
||||
if (postProcess) {
|
||||
postProcess(pokemon);
|
||||
}
|
||||
|
||||
if (Overrides.IVS_OVERRIDE === null) {
|
||||
// do nothing
|
||||
} else if (Array.isArray(Overrides.IVS_OVERRIDE)) {
|
||||
if (Overrides.IVS_OVERRIDE.length !== 6) {
|
||||
throw new Error("The Player IVs override must be an array of length 6 or a number!");
|
||||
}
|
||||
if (Overrides.IVS_OVERRIDE.some(value => !isBetween(value, 0, 31))) {
|
||||
throw new Error("All IVs in the player IV override must be between 0 and 31!");
|
||||
}
|
||||
pokemon.ivs = Overrides.IVS_OVERRIDE;
|
||||
} else {
|
||||
if (!isBetween(Overrides.IVS_OVERRIDE, 0, 31)) {
|
||||
throw new Error("The Player IV override must be a value between 0 and 31!");
|
||||
}
|
||||
pokemon.ivs = new Array(6).fill(Overrides.IVS_OVERRIDE);
|
||||
}
|
||||
|
||||
if (Overrides.NATURE_OVERRIDE !== null) {
|
||||
pokemon.nature = Overrides.NATURE_OVERRIDE;
|
||||
}
|
||||
|
||||
pokemon.init();
|
||||
return pokemon;
|
||||
}
|
||||
@ -981,10 +1003,25 @@ export default class BattleScene extends SceneBase {
|
||||
postProcess(pokemon);
|
||||
}
|
||||
|
||||
for (let i = 0; i < pokemon.ivs.length; i++) {
|
||||
if (OPP_IVS_OVERRIDE_VALIDATED[i] > -1) {
|
||||
pokemon.ivs[i] = OPP_IVS_OVERRIDE_VALIDATED[i];
|
||||
if (Overrides.ENEMY_IVS_OVERRIDE === null) {
|
||||
// do nothing
|
||||
} else if (Array.isArray(Overrides.ENEMY_IVS_OVERRIDE)) {
|
||||
if (Overrides.ENEMY_IVS_OVERRIDE.length !== 6) {
|
||||
throw new Error("The Enemy IVs override must be an array of length 6 or a number!");
|
||||
}
|
||||
if (Overrides.ENEMY_IVS_OVERRIDE.some(value => !isBetween(value, 0, 31))) {
|
||||
throw new Error("All IVs in the enemy IV override must be between 0 and 31!");
|
||||
}
|
||||
pokemon.ivs = Overrides.ENEMY_IVS_OVERRIDE;
|
||||
} else {
|
||||
if (!isBetween(Overrides.ENEMY_IVS_OVERRIDE, 0, 31)) {
|
||||
throw new Error("The Enemy IV override must be a value between 0 and 31!");
|
||||
}
|
||||
pokemon.ivs = new Array(6).fill(Overrides.ENEMY_IVS_OVERRIDE);
|
||||
}
|
||||
|
||||
if (Overrides.ENEMY_NATURE_OVERRIDE !== null) {
|
||||
pokemon.nature = Overrides.ENEMY_NATURE_OVERRIDE;
|
||||
}
|
||||
|
||||
pokemon.init();
|
||||
@ -2488,6 +2525,10 @@ export default class BattleScene extends SceneBase {
|
||||
return 10.344;
|
||||
case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle
|
||||
return 11.424;
|
||||
case "battle_legendary_eternatus_p1": //SWSH Eternatus Battle
|
||||
return 11.102;
|
||||
case "battle_legendary_eternatus_p2": //SWSH Eternamax Eternatus Battle
|
||||
return 0.0;
|
||||
case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle
|
||||
return 12.503;
|
||||
case "battle_legendary_calyrex": //SWSH Calyrex Battle
|
||||
|
@ -17,7 +17,8 @@ import { MoneyMultiplierModifier, type PokemonHeldItemModifier } from "./modifie
|
||||
import type { PokeballType } from "#enums/pokeball";
|
||||
import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
import type { EnemyPokemon, PlayerPokemon, TurnMove } from "#app/field/pokemon";
|
||||
import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import type { TurnMove } from "./@types/turn-move";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { BattleSpec } from "#enums/battle-spec";
|
||||
@ -94,6 +95,12 @@ export default class Battle {
|
||||
/** If the current battle is a Mystery Encounter, this will always be defined */
|
||||
public mysteryEncounter?: MysteryEncounter;
|
||||
|
||||
/**
|
||||
* Tracker for whether the last run attempt failed.
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
public failedRunAway = false;
|
||||
|
||||
private rngCounter = 0;
|
||||
|
||||
constructor(gameMode: GameMode, waveIndex: number, battleType: BattleType, trainer?: Trainer, double = false) {
|
||||
@ -375,6 +382,11 @@ export default class Battle {
|
||||
case SpeciesId.ZACIAN:
|
||||
case SpeciesId.ZAMAZENTA:
|
||||
return "battle_legendary_zac_zam";
|
||||
case SpeciesId.ETERNATUS:
|
||||
if (pokemon.getFormKey() === "eternamax") {
|
||||
return "battle_legendary_eternatus_p2";
|
||||
}
|
||||
return "battle_legendary_eternatus_p1";
|
||||
case SpeciesId.GLASTRIER:
|
||||
case SpeciesId.SPECTRIER:
|
||||
return "battle_legendary_glas_spec";
|
||||
|
@ -1194,7 +1194,16 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
/**
|
||||
* Set stat stages when the user gets hit by a critical hit
|
||||
*
|
||||
* @privateremarks
|
||||
* It is the responsibility of the caller to ensure that this ability attribute is only applied
|
||||
* when the user has been hit by a critical hit; such an event is not checked here.
|
||||
*
|
||||
* @sealed
|
||||
*/
|
||||
export class PostReceiveCritStatStageChangeAbAttr extends AbAttr {
|
||||
private stat: BattleStat;
|
||||
private stages: number;
|
||||
|
||||
@ -1216,12 +1225,6 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
override getCondition(): AbAttrCondition {
|
||||
return (pokemon: Pokemon) =>
|
||||
pokemon.turnData.attacksReceived.length !== 0 &&
|
||||
pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
|
||||
}
|
||||
}
|
||||
|
||||
export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
|
||||
@ -6427,7 +6430,7 @@ const AbilityAttrs = Object.freeze({
|
||||
PostDefendContactApplyStatusEffectAbAttr,
|
||||
EffectSporeAbAttr,
|
||||
PostDefendContactApplyTagChanceAbAttr,
|
||||
PostDefendCritStatStageChangeAbAttr,
|
||||
PostReceiveCritStatStageChangeAbAttr,
|
||||
PostDefendContactDamageAbAttr,
|
||||
PostDefendPerishSongAbAttr,
|
||||
PostDefendWeatherChangeAbAttr,
|
||||
@ -6896,7 +6899,7 @@ export function initAbilities() {
|
||||
new Ability(AbilityId.GLUTTONY, 4)
|
||||
.attr(ReduceBerryUseThresholdAbAttr),
|
||||
new Ability(AbilityId.ANGER_POINT, 4)
|
||||
.attr(PostDefendCritStatStageChangeAbAttr, Stat.ATK, 6),
|
||||
.attr(PostReceiveCritStatStageChangeAbAttr, Stat.ATK, 12),
|
||||
new Ability(AbilityId.UNBURDEN, 4)
|
||||
.attr(PostItemLostApplyBattlerTagAbAttr, BattlerTagType.UNBURDEN)
|
||||
.bypassFaint() // Allows reviver seed to activate Unburden
|
||||
@ -7273,11 +7276,14 @@ export function initAbilities() {
|
||||
new Ability(AbilityId.MERCILESS, 7)
|
||||
.attr(ConditionalCritAbAttr, (_user, target, _move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON),
|
||||
new Ability(AbilityId.SHIELDS_DOWN, 7, -1)
|
||||
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
||||
// Change into Meteor Form on switch-in or turn end if HP >= 50%,
|
||||
// or Core Form if HP <= 50%.
|
||||
.attr(PostBattleInitFormChangeAbAttr, p => p.formIndex % 7)
|
||||
.attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
|
||||
.attr(PostTurnFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
|
||||
.conditionalAttr(p => p.formIndex !== 7, StatusEffectImmunityAbAttr)
|
||||
.conditionalAttr(p => p.formIndex !== 7, BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
||||
// All variants of Meteor Form are immune to status effects & Yawn
|
||||
.conditionalAttr(p => p.formIndex < 7, StatusEffectImmunityAbAttr)
|
||||
.conditionalAttr(p => p.formIndex < 7, BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
||||
.attr(NoFusionAbilityAbAttr)
|
||||
.attr(NoTransformAbilityAbAttr)
|
||||
.uncopiable()
|
||||
@ -7343,12 +7349,11 @@ export function initAbilities() {
|
||||
.unsuppressable()
|
||||
.bypassFaint(),
|
||||
new Ability(AbilityId.POWER_CONSTRUCT, 7)
|
||||
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostBattleInitFormChangeAbAttr, () => 2)
|
||||
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostBattleInitFormChangeAbAttr, () => 3)
|
||||
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
|
||||
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
|
||||
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3)
|
||||
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3)
|
||||
// Change to 10% complete or 50% complete on switchout/turn end if at <50% HP;
|
||||
// revert to 10% PC or 50% PC before a new battle starts
|
||||
.conditionalAttr(p => p.formIndex === 4 || p.formIndex === 5, PostBattleInitFormChangeAbAttr, p => p.formIndex - 2)
|
||||
.conditionalAttr(p => p.getHpRatio() <= 0.5 && (p.formIndex === 2 || p.formIndex === 3), PostSummonFormChangeAbAttr, p => p.formIndex + 2)
|
||||
.conditionalAttr(p => p.getHpRatio() <= 0.5 && (p.formIndex === 2 || p.formIndex === 3), PostTurnFormChangeAbAttr, p => p.formIndex + 2)
|
||||
.attr(NoFusionAbilityAbAttr)
|
||||
.uncopiable()
|
||||
.unreplaceable()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -880,6 +880,10 @@ export abstract class BattleAnim {
|
||||
targetSprite.pipelineData["tone"] = [0.0, 0.0, 0.0, 0.0];
|
||||
targetSprite.setAngle(0);
|
||||
|
||||
// Remove animation event listeners to enable sprites to be freed.
|
||||
userSprite.off("animationupdate");
|
||||
targetSprite.off("animationupdate");
|
||||
|
||||
/**
|
||||
* This and `targetSpriteToShow` are used to restore context lost
|
||||
* from the `isOppAnim` swap. Using these references instead of `this.user`
|
||||
|
@ -673,7 +673,12 @@ export class ConfusedTag extends BattlerTag {
|
||||
}
|
||||
|
||||
canAdd(pokemon: Pokemon): boolean {
|
||||
return globalScene.arena.terrain?.terrainType !== TerrainType.MISTY || !pokemon.isGrounded();
|
||||
const blockedByTerrain = pokemon.isGrounded() && globalScene.arena.terrain?.terrainType === TerrainType.MISTY;
|
||||
if (blockedByTerrain) {
|
||||
pokemon.queueStatusImmuneMessage(false, TerrainType.MISTY);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
onAdd(pokemon: Pokemon): void {
|
||||
|
@ -1,31 +0,0 @@
|
||||
import type { AbilityId } from "#enums/ability-id";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import type { Nature } from "#enums/nature";
|
||||
|
||||
/**
|
||||
* Data that can customize a Pokemon in non-standard ways from its Species.
|
||||
* Includes abilities, nature, changed types, etc.
|
||||
*/
|
||||
export class CustomPokemonData {
|
||||
// TODO: Change the default value for all these from -1 to something a bit more sensible
|
||||
/**
|
||||
* The scale at which to render this Pokemon's sprite.
|
||||
*/
|
||||
public spriteScale = -1;
|
||||
public ability: AbilityId | -1;
|
||||
public passive: AbilityId | -1;
|
||||
public nature: Nature | -1;
|
||||
public types: PokemonType[];
|
||||
/** Deprecated but needed for session save migration */
|
||||
// TODO: Remove this once pre-session migration is implemented
|
||||
public hitsRecCount: number | null = null;
|
||||
|
||||
constructor(data?: CustomPokemonData | Partial<CustomPokemonData>) {
|
||||
this.spriteScale = data?.spriteScale ?? -1;
|
||||
this.ability = data?.ability ?? -1;
|
||||
this.passive = data?.passive ?? -1;
|
||||
this.nature = data?.nature ?? -1;
|
||||
this.types = data?.types ?? [];
|
||||
this.hitsRecCount = data?.hitsRecCount ?? null;
|
||||
}
|
||||
}
|
@ -294,7 +294,7 @@ export class Egg {
|
||||
|
||||
public getEggDescriptor(): string {
|
||||
if (this.isManaphyEgg()) {
|
||||
return "Manaphy";
|
||||
return i18next.t("egg:manaphyTier");
|
||||
}
|
||||
switch (this.tier) {
|
||||
case EggTier.RARE:
|
||||
|
@ -13,7 +13,8 @@ import {
|
||||
TypeBoostTag,
|
||||
} from "../battler-tags";
|
||||
import { getPokemonNameWithAffix } from "../../messages";
|
||||
import type { AttackMoveResult, TurnMove } from "../../field/pokemon";
|
||||
import type { TurnMove } from "#app/@types/turn-move";
|
||||
import type { AttackMoveResult } from "#app/@types/attack-move-result";
|
||||
import type Pokemon from "../../field/pokemon";
|
||||
import type { EnemyPokemon } from "#app/field/pokemon";
|
||||
import { PokemonMove } from "./pokemon-move";
|
||||
|
@ -44,7 +44,7 @@ import { BattlerIndex } from "#enums/battler-index";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { EncounterBattleAnim } from "#app/data/battle-anims";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import { CustomPokemonData } from "#app/data/pokemon/pokemon-data";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
|
@ -29,7 +29,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import { CustomPokemonData } from "#app/data/pokemon/pokemon-data";
|
||||
import { randSeedInt } from "#app/utils/common";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
||||
|
@ -25,7 +25,7 @@ import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import { CustomPokemonData } from "#app/data/pokemon/pokemon-data";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
@ -54,7 +54,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "berry_juice",
|
||||
spriteKey: "berry_juice_good",
|
||||
fileRoot: "items",
|
||||
hasShadow: true,
|
||||
isItem: true,
|
||||
@ -171,11 +171,11 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
sortedParty.forEach((pokemon, index) => {
|
||||
if (index < 2) {
|
||||
// -15 to the two highest BST mons
|
||||
modifyPlayerPokemonBST(pokemon, -HIGH_BST_REDUCTION_VALUE);
|
||||
modifyPlayerPokemonBST(pokemon, false);
|
||||
encounter.setDialogueToken("highBstPokemon" + (index + 1), pokemon.getNameToRender());
|
||||
} else {
|
||||
// +10 for the rest
|
||||
modifyPlayerPokemonBST(pokemon, BST_INCREASE_VALUE);
|
||||
modifyPlayerPokemonBST(pokemon, true);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -15,7 +15,7 @@ import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import { AbilityAttr } from "#enums/ability-attr";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { isNullOrUndefined, randSeedShuffle } from "#app/utils/common";
|
||||
import { getEnumValues, isNullOrUndefined, randSeedShuffle } from "#app/utils/common";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
@ -30,7 +30,7 @@ import i18next from "i18next";
|
||||
import { getStatKey } from "#enums/stat";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import type { Nature } from "#enums/nature";
|
||||
import { Nature } from "#enums/nature";
|
||||
|
||||
/** The i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounters/trainingSession";
|
||||
@ -184,10 +184,9 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
// Open menu for selecting pokemon and Nature
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const natures = new Array(25).fill(null).map((_val, i) => i as Nature);
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for nature selection
|
||||
return natures.map((nature: Nature) => {
|
||||
return getEnumValues(Nature).map((nature: Nature) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: getNatureName(nature, true, true, true, globalScene.uiTheme),
|
||||
handler: () => {
|
||||
|
@ -33,7 +33,6 @@ import {
|
||||
TransformationScreenPosition,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
|
||||
import { getLevelTotalExp } from "#app/data/exp";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
@ -104,8 +103,6 @@ const EXCLUDED_TRANSFORMATION_SPECIES = [
|
||||
const SUPER_LEGENDARY_BST_THRESHOLD = 600;
|
||||
const NON_LEGENDARY_BST_THRESHOLD = 570;
|
||||
|
||||
const OLD_GATEAU_STATS_UP = 20;
|
||||
|
||||
/** 0-100 */
|
||||
const PERCENT_LEVEL_LOSS_ON_REFUSE = 10;
|
||||
|
||||
@ -275,12 +272,8 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
||||
}
|
||||
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
||||
if (shouldGetOldGateau(newPokemon)) {
|
||||
const stats = getOldGateauBoostedStats(newPokemon);
|
||||
newPokemonHeldItemConfigs.push({
|
||||
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [
|
||||
OLD_GATEAU_STATS_UP,
|
||||
stats,
|
||||
]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false,
|
||||
});
|
||||
@ -461,11 +454,7 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
|
||||
}
|
||||
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
||||
if (shouldGetOldGateau(newPokemon)) {
|
||||
const stats = getOldGateauBoostedStats(newPokemon);
|
||||
const modType = modifierTypes
|
||||
.MYSTERY_ENCOUNTER_OLD_GATEAU()
|
||||
.generateType(globalScene.getPlayerParty(), [OLD_GATEAU_STATS_UP, stats])
|
||||
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
|
||||
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU();
|
||||
const modifier = modType?.newModifier(newPokemon);
|
||||
if (modifier) {
|
||||
globalScene.addModifier(modifier, false, false, false, true);
|
||||
@ -616,22 +605,6 @@ function shouldGetOldGateau(pokemon: Pokemon): boolean {
|
||||
return pokemon.getSpeciesForm().getBaseStatTotal() < NON_LEGENDARY_BST_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef
|
||||
* @returns Array of 3 {@linkcode Stat}s to boost
|
||||
*/
|
||||
function getOldGateauBoostedStats(pokemon: Pokemon): Stat[] {
|
||||
const stats: Stat[] = [];
|
||||
const baseStats = pokemon.getSpeciesForm().baseStats.slice(0);
|
||||
// HP or Speed
|
||||
stats.push(baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD);
|
||||
// Attack or SpAtk
|
||||
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
|
||||
// Def or SpDef
|
||||
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
|
||||
return stats;
|
||||
}
|
||||
|
||||
function getTransformedSpecies(
|
||||
originalBst: number,
|
||||
bstSearchRange: [number, number],
|
||||
|
@ -43,7 +43,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import type PokemonSpecies from "#app/data/pokemon-species";
|
||||
import type { IEggOptions } from "#app/data/egg";
|
||||
import { Egg } from "#app/data/egg";
|
||||
import type { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import type { CustomPokemonData } from "#app/data/pokemon/pokemon-data";
|
||||
import type HeldModifierConfig from "#app/@types/held-modifier-config";
|
||||
import type { Variant } from "#app/sprites/variant";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
|
@ -33,7 +33,7 @@ import { modifierTypes } from "#app/data/data-lists";
|
||||
import { Gender } from "#app/data/gender";
|
||||
import type { PermanentStat } from "#enums/stat";
|
||||
import { SummaryUiMode } from "#app/ui/summary-ui-handler";
|
||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import { CustomPokemonData } from "#app/data/pokemon/pokemon-data";
|
||||
import type { AbilityId } from "#enums/ability-id";
|
||||
import type { PokeballType } from "#enums/pokeball";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
@ -375,10 +375,10 @@ export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) {
|
||||
* @param pokemon
|
||||
* @param value
|
||||
*/
|
||||
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
|
||||
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, good: boolean) {
|
||||
const modType = modifierTypes
|
||||
.MYSTERY_ENCOUNTER_SHUCKLE_JUICE()
|
||||
.generateType(globalScene.getPlayerParty(), [value])
|
||||
.generateType(globalScene.getPlayerParty(), [good ? 10 : -15])
|
||||
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE);
|
||||
const modifier = modType?.newModifier(pokemon);
|
||||
if (modifier) {
|
||||
|
208
src/data/pokemon/pokemon-data.ts
Normal file
208
src/data/pokemon/pokemon-data.ts
Normal file
@ -0,0 +1,208 @@
|
||||
import { type BattlerTag, loadBattlerTag } from "#app/data/battler-tags";
|
||||
import type { Gender } from "#app/data/gender";
|
||||
import type { PokemonSpeciesForm } from "#app/data/pokemon-species";
|
||||
import type { TypeDamageMultiplier } from "#app/data/type";
|
||||
import { isNullOrUndefined } from "#app/utils/common";
|
||||
import type { AbilityId } from "#enums/ability-id";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import type { TurnMove } from "#app/@types/turn-move";
|
||||
import type { AttackMoveResult } from "#app/@types/attack-move-result";
|
||||
import type { Nature } from "#enums/nature";
|
||||
import type { IllusionData } from "#app/@types/illusion-data";
|
||||
|
||||
/**
|
||||
* Permanent data that can customize a Pokemon in non-standard ways from its Species.
|
||||
* Includes abilities, nature, changed types, etc.
|
||||
*/
|
||||
export class CustomPokemonData {
|
||||
// TODO: Change the default value for all these from -1 to something a bit more sensible
|
||||
/**
|
||||
* The scale at which to render this Pokemon's sprite.
|
||||
*/
|
||||
public spriteScale = -1;
|
||||
public ability: AbilityId | -1;
|
||||
public passive: AbilityId | -1;
|
||||
public nature: Nature | -1;
|
||||
public types: PokemonType[];
|
||||
/** Deprecated but needed for session save migration */
|
||||
// TODO: Remove this once pre-session migration is implemented
|
||||
public hitsRecCount: number | null = null;
|
||||
|
||||
constructor(data?: CustomPokemonData | Partial<CustomPokemonData>) {
|
||||
this.spriteScale = data?.spriteScale ?? -1;
|
||||
this.ability = data?.ability ?? -1;
|
||||
this.passive = data?.passive ?? -1;
|
||||
this.nature = data?.nature ?? -1;
|
||||
this.types = data?.types ?? [];
|
||||
this.hitsRecCount = data?.hitsRecCount ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistent in-battle data for a {@linkcode Pokemon}.
|
||||
* Resets on switch or new battle.
|
||||
*/
|
||||
export class PokemonSummonData {
|
||||
/** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */
|
||||
public statStages: number[] = [0, 0, 0, 0, 0, 0, 0];
|
||||
/**
|
||||
* A queue of moves yet to be executed, used by charging, recharging and frenzy moves.
|
||||
* So long as this array is nonempty, this Pokemon's corresponding `CommandPhase` will be skipped over entirely
|
||||
* in favor of using the queued move.
|
||||
* TODO: Clean up a lot of the code surrounding the move queue.
|
||||
*/
|
||||
public moveQueue: TurnMove[] = [];
|
||||
public tags: BattlerTag[] = [];
|
||||
public abilitySuppressed = false;
|
||||
|
||||
// Overrides for transform.
|
||||
// TODO: Move these into a separate class & add rage fist hit count
|
||||
public speciesForm: PokemonSpeciesForm | null = null;
|
||||
public fusionSpeciesForm: PokemonSpeciesForm | null = null;
|
||||
public ability: AbilityId | undefined;
|
||||
public passiveAbility: AbilityId | undefined;
|
||||
public gender: Gender | undefined;
|
||||
public fusionGender: Gender | undefined;
|
||||
public stats: number[] = [0, 0, 0, 0, 0, 0];
|
||||
public moveset: PokemonMove[] | null;
|
||||
|
||||
// If not initialized this value will not be populated from save data.
|
||||
public types: PokemonType[] = [];
|
||||
public addedType: PokemonType | null = null;
|
||||
|
||||
/** Data pertaining to this pokemon's illusion. */
|
||||
public illusion: IllusionData | null = null;
|
||||
public illusionBroken = false;
|
||||
|
||||
/** Array containing all berries eaten in the last turn; used by {@linkcode AbilityId.CUD_CHEW} */
|
||||
public berriesEatenLast: BerryType[] = [];
|
||||
|
||||
/**
|
||||
* An array of all moves this pokemon has used since entering the battle.
|
||||
* Used for most moves and abilities that check prior move usage or copy already-used moves.
|
||||
*/
|
||||
public moveHistory: TurnMove[] = [];
|
||||
|
||||
constructor(source?: PokemonSummonData | Partial<PokemonSummonData>) {
|
||||
if (isNullOrUndefined(source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Rework this into an actual generic function for use elsewhere
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
if (isNullOrUndefined(value) && this.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === "moveset") {
|
||||
this.moveset = value?.map((m: any) => PokemonMove.loadMove(m));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === "tags") {
|
||||
// load battler tags
|
||||
this.tags = value.map((t: BattlerTag) => loadBattlerTag(t));
|
||||
continue;
|
||||
}
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added
|
||||
export class PokemonTempSummonData {
|
||||
/**
|
||||
* The number of turns this pokemon has spent without switching out.
|
||||
* Only currently used for positioning the battle cursor.
|
||||
*/
|
||||
turnCount = 1;
|
||||
|
||||
/**
|
||||
* The number of turns this pokemon has spent in the active position since the start of the wave
|
||||
* without switching out.
|
||||
* Reset on switch and new wave, but not stored in `SummonData` to avoid being written to the save file.
|
||||
|
||||
* Used to evaluate "first turn only" conditions such as
|
||||
* {@linkcode MoveId.FAKE_OUT | Fake Out} and {@linkcode MoveId.FIRST_IMPRESSION | First Impression}).
|
||||
*/
|
||||
waveTurnCount = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistent data for a {@linkcode Pokemon}.
|
||||
* Resets at the start of a new battle (but not on switch).
|
||||
*/
|
||||
export class PokemonBattleData {
|
||||
/** Counter tracking direct hits this Pokemon has received during this battle; used for {@linkcode MoveId.RAGE_FIST} */
|
||||
public hitCount = 0;
|
||||
/** Whether this Pokemon has eaten a berry this battle; used for {@linkcode MoveId.BELCH} */
|
||||
public hasEatenBerry = false;
|
||||
/** Array containing all berries eaten and not yet recovered during this current battle; used by {@linkcode AbilityId.HARVEST} */
|
||||
public berriesEaten: BerryType[] = [];
|
||||
|
||||
constructor(source?: PokemonBattleData | Partial<PokemonBattleData>) {
|
||||
if (!isNullOrUndefined(source)) {
|
||||
this.hitCount = source.hitCount ?? 0;
|
||||
this.hasEatenBerry = source.hasEatenBerry ?? false;
|
||||
this.berriesEaten = source.berriesEaten ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary data for a {@linkcode Pokemon}.
|
||||
* Resets on new wave/battle start (but not on switch).
|
||||
*/
|
||||
export class PokemonWaveData {
|
||||
/** Whether the pokemon has endured due to a {@linkcode BattlerTagType.ENDURE_TOKEN} */
|
||||
public endured = false;
|
||||
/**
|
||||
* A set of all the abilities this {@linkcode Pokemon} has used in this wave.
|
||||
* Used to track once per battle conditions, as well as (hopefully) by the updated AI for move effectiveness.
|
||||
*/
|
||||
public abilitiesApplied: Set<AbilityId> = new Set<AbilityId>();
|
||||
/** Whether the pokemon's ability has been revealed or not */
|
||||
public abilityRevealed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary data for a {@linkcode Pokemon}.
|
||||
* Resets at the start of a new turn, as well as on switch.
|
||||
*/
|
||||
export class PokemonTurnData {
|
||||
public acted = false;
|
||||
/** How many times the current move should hit the target(s) */
|
||||
public hitCount = 0;
|
||||
/**
|
||||
* - `-1` = Calculate how many hits are left
|
||||
* - `0` = Move is finished
|
||||
*/
|
||||
public hitsLeft = -1;
|
||||
public totalDamageDealt = 0;
|
||||
public singleHitDamageDealt = 0;
|
||||
public damageTaken = 0;
|
||||
public attacksReceived: AttackMoveResult[] = [];
|
||||
public order: number;
|
||||
public statStagesIncreased = false;
|
||||
public statStagesDecreased = false;
|
||||
public moveEffectiveness: TypeDamageMultiplier | null = null;
|
||||
public combiningPledge?: MoveId;
|
||||
public switchedInThisTurn = false;
|
||||
public failedRunAway = false;
|
||||
public joinedRound = false;
|
||||
/**
|
||||
* The amount of times this Pokemon has acted again and used a move in the current turn.
|
||||
* Used to make sure multi-hits occur properly when the user is
|
||||
* forced to act again in the same turn, and **must be incremented** by any effects that grant extra actions.
|
||||
*/
|
||||
public extraTurns = 0;
|
||||
/**
|
||||
* All berries eaten by this pokemon in this turn.
|
||||
* Saved into {@linkcode PokemonSummonData | SummonData} by {@linkcode AbilityId.CUD_CHEW} on turn end.
|
||||
* @see {@linkcode PokemonSummonData.berriesEatenLast}
|
||||
*/
|
||||
public berriesEaten: BerryType[] = [];
|
||||
}
|
@ -3,6 +3,7 @@ import type Move from "./moves/move";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import i18next from "i18next";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
|
||||
export enum TerrainType {
|
||||
NONE,
|
||||
@ -96,3 +97,76 @@ export function getTerrainColor(terrainType: TerrainType): [number, number, numb
|
||||
|
||||
return [0, 0, 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the message associated with a terrain effect starting.
|
||||
* @param terrainType - The {@linkcode TerrainType} starting.
|
||||
* @returns A string containing the appropriate terrain start text.
|
||||
*/
|
||||
export function getTerrainStartMessage(terrainType: TerrainType): string {
|
||||
switch (terrainType) {
|
||||
case TerrainType.MISTY:
|
||||
return i18next.t("terrain:mistyStartMessage");
|
||||
case TerrainType.ELECTRIC:
|
||||
return i18next.t("terrain:electricStartMessage");
|
||||
case TerrainType.GRASSY:
|
||||
return i18next.t("terrain:grassyStartMessage");
|
||||
case TerrainType.PSYCHIC:
|
||||
return i18next.t("terrain:psychicStartMessage");
|
||||
case TerrainType.NONE:
|
||||
default:
|
||||
terrainType satisfies TerrainType.NONE;
|
||||
console.warn(`${terrainType} unexpectedly provided as terrain type to getTerrainStartMessage!`);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the message associated with a terrain effect ceasing to exist.
|
||||
* @param terrainType - The {@linkcode TerrainType} being cleared.
|
||||
* @returns A string containing the appropriate terrain clear text.
|
||||
*/
|
||||
export function getTerrainClearMessage(terrainType: TerrainType): string {
|
||||
switch (terrainType) {
|
||||
case TerrainType.MISTY:
|
||||
return i18next.t("terrain:mistyClearMessage");
|
||||
case TerrainType.ELECTRIC:
|
||||
return i18next.t("terrain:electricClearMessage");
|
||||
case TerrainType.GRASSY:
|
||||
return i18next.t("terrain:grassyClearMessage");
|
||||
case TerrainType.PSYCHIC:
|
||||
return i18next.t("terrain:psychicClearMessage");
|
||||
case TerrainType.NONE:
|
||||
default:
|
||||
terrainType satisfies TerrainType.NONE;
|
||||
console.warn(`${terrainType} unexpectedly provided as terrain type to getTerrainClearMessage!`);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the message associated with a terrain-induced move/effect blockage.
|
||||
* @param pokemon - The {@linkcode Pokemon} being protected.
|
||||
* @param terrainType - The {@linkcode TerrainType} in question
|
||||
* @returns A string containing the appropriate terrain block text.
|
||||
*/
|
||||
export function getTerrainBlockMessage(pokemon: Pokemon, terrainType: TerrainType): string {
|
||||
switch (terrainType) {
|
||||
case TerrainType.MISTY:
|
||||
return i18next.t("terrain:mistyBlockMessage", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
});
|
||||
case TerrainType.ELECTRIC:
|
||||
case TerrainType.GRASSY:
|
||||
case TerrainType.PSYCHIC:
|
||||
return i18next.t("terrain:defaultBlockMessage", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
terrainName: getTerrainName(terrainType),
|
||||
});
|
||||
case TerrainType.NONE:
|
||||
default:
|
||||
terrainType satisfies TerrainType.NONE;
|
||||
console.warn(`${terrainType} unexpectedly provided as terrain type to getTerrainBlockMessage!`);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import type Pokemon from "../field/pokemon";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import type Move from "./moves/move";
|
||||
import { randSeedInt } from "#app/utils/common";
|
||||
import { TerrainType, getTerrainName } from "./terrain";
|
||||
import i18next from "i18next";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { Arena } from "#app/field/arena";
|
||||
@ -235,50 +234,6 @@ export function getWeatherBlockMessage(weatherType: WeatherType): string {
|
||||
return i18next.t("weather:defaultEffectMessage");
|
||||
}
|
||||
|
||||
export function getTerrainStartMessage(terrainType: TerrainType): string | null {
|
||||
switch (terrainType) {
|
||||
case TerrainType.MISTY:
|
||||
return i18next.t("terrain:mistyStartMessage");
|
||||
case TerrainType.ELECTRIC:
|
||||
return i18next.t("terrain:electricStartMessage");
|
||||
case TerrainType.GRASSY:
|
||||
return i18next.t("terrain:grassyStartMessage");
|
||||
case TerrainType.PSYCHIC:
|
||||
return i18next.t("terrain:psychicStartMessage");
|
||||
default:
|
||||
console.warn("getTerrainStartMessage not defined. Using default null");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getTerrainClearMessage(terrainType: TerrainType): string | null {
|
||||
switch (terrainType) {
|
||||
case TerrainType.MISTY:
|
||||
return i18next.t("terrain:mistyClearMessage");
|
||||
case TerrainType.ELECTRIC:
|
||||
return i18next.t("terrain:electricClearMessage");
|
||||
case TerrainType.GRASSY:
|
||||
return i18next.t("terrain:grassyClearMessage");
|
||||
case TerrainType.PSYCHIC:
|
||||
return i18next.t("terrain:psychicClearMessage");
|
||||
default:
|
||||
console.warn("getTerrainClearMessage not defined. Using default null");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getTerrainBlockMessage(pokemon: Pokemon, terrainType: TerrainType): string {
|
||||
if (terrainType === TerrainType.MISTY) {
|
||||
return i18next.t("terrain:mistyBlockMessage", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
});
|
||||
}
|
||||
return i18next.t("terrain:defaultBlockMessage", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
terrainName: getTerrainName(terrainType),
|
||||
});
|
||||
}
|
||||
|
||||
export interface WeatherPoolEntry {
|
||||
weatherType: WeatherType;
|
||||
weight: number;
|
||||
|
@ -1,5 +1,7 @@
|
||||
export enum GachaType {
|
||||
MOVE,
|
||||
LEGENDARY,
|
||||
SHINY
|
||||
}
|
||||
export const GachaType = Object.freeze({
|
||||
MOVE: 0,
|
||||
LEGENDARY: 1,
|
||||
SHINY: 2
|
||||
});
|
||||
|
||||
export type GachaType = typeof GachaType[keyof typeof GachaType];
|
||||
|
@ -150,7 +150,7 @@ function doFanOutParticle(
|
||||
}
|
||||
|
||||
export function addPokeballCaptureStars(pokeball: Phaser.GameObjects.Sprite): void {
|
||||
const addParticle = () => {
|
||||
const addParticle = (): void => {
|
||||
const particle = globalScene.add.sprite(pokeball.x, pokeball.y, "pb_particles", "4.png");
|
||||
particle.setOrigin(pokeball.originX, pokeball.originY);
|
||||
particle.setAlpha(0.5);
|
||||
@ -188,7 +188,9 @@ export function addPokeballCaptureStars(pokeball: Phaser.GameObjects.Sprite): vo
|
||||
});
|
||||
};
|
||||
|
||||
new Array(3).fill(null).map(() => addParticle());
|
||||
for (let i = 0; i < 3; i++) {
|
||||
addParticle();
|
||||
}
|
||||
}
|
||||
|
||||
export function sin(index: number, amplitude: number): number {
|
||||
|
@ -5,8 +5,6 @@ import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "
|
||||
import type PokemonSpecies from "#app/data/pokemon-species";
|
||||
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
|
||||
import {
|
||||
getTerrainClearMessage,
|
||||
getTerrainStartMessage,
|
||||
getWeatherClearMessage,
|
||||
getWeatherStartMessage,
|
||||
getLegendaryWeatherContinuesMessage,
|
||||
@ -19,7 +17,7 @@ import type { ArenaTag } from "#app/data/arena-tag";
|
||||
import { ArenaTrapTag, getArenaTag } from "#app/data/arena-tag";
|
||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { Terrain, TerrainType } from "#app/data/terrain";
|
||||
import { Terrain, TerrainType, getTerrainClearMessage, getTerrainStartMessage } from "#app/data/terrain";
|
||||
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import Overrides from "#app/overrides";
|
||||
@ -445,9 +443,9 @@ export class Arena {
|
||||
CommonAnim.MISTY_TERRAIN + (terrain - 1),
|
||||
);
|
||||
}
|
||||
globalScene.phaseManager.queueMessage(getTerrainStartMessage(terrain)!); // TODO: is this bang correct?
|
||||
globalScene.phaseManager.queueMessage(getTerrainStartMessage(terrain));
|
||||
} else {
|
||||
globalScene.phaseManager.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct?
|
||||
globalScene.phaseManager.queueMessage(getTerrainClearMessage(oldTerrainType));
|
||||
}
|
||||
|
||||
globalScene
|
||||
@ -899,7 +897,7 @@ export class Arena {
|
||||
case BiomeId.WASTELAND:
|
||||
return 6.336;
|
||||
case BiomeId.ABYSS:
|
||||
return 5.13;
|
||||
return 20.113;
|
||||
case BiomeId.SPACE:
|
||||
return 20.036;
|
||||
case BiomeId.CONSTRUCTION_SITE:
|
||||
@ -983,14 +981,15 @@ export class ArenaBase extends Phaser.GameObjects.Container {
|
||||
this.base = globalScene.addFieldSprite(0, 0, "plains_a", undefined, 1);
|
||||
this.base.setOrigin(0, 0);
|
||||
|
||||
this.props = !player
|
||||
? new Array(3).fill(null).map(() => {
|
||||
const ret = globalScene.addFieldSprite(0, 0, "plains_b", undefined, 1);
|
||||
ret.setOrigin(0, 0);
|
||||
ret.setVisible(false);
|
||||
return ret;
|
||||
})
|
||||
: [];
|
||||
this.props = [];
|
||||
if (!player) {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const ret = globalScene.addFieldSprite(0, 0, "plains_b", undefined, 1);
|
||||
ret.setOrigin(0, 0);
|
||||
ret.setVisible(false);
|
||||
this.props.push(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setBiome(biome: BiomeId, propValue?: number): void {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TextStyle, addTextObject } from "../ui/text";
|
||||
import type { DamageResult } from "./pokemon";
|
||||
import type { DamageResult } from "../@types/damage-result";
|
||||
import type Pokemon from "./pokemon";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { formatStat, fixedInt } from "#app/utils/common";
|
||||
|
@ -100,7 +100,6 @@ import {
|
||||
TarShotTag,
|
||||
AutotomizedTag,
|
||||
PowerTrickTag,
|
||||
loadBattlerTag,
|
||||
type GrudgeTag,
|
||||
} from "../data/battler-tags";
|
||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||
@ -151,7 +150,14 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { PokemonAnimType } from "#enums/pokemon-anim-type";
|
||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import {
|
||||
CustomPokemonData,
|
||||
PokemonBattleData,
|
||||
PokemonSummonData,
|
||||
PokemonTempSummonData,
|
||||
PokemonTurnData,
|
||||
PokemonWaveData,
|
||||
} from "#app/data/pokemon/pokemon-data";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
import { getStatusEffectOverlapText } from "#app/data/status-effect";
|
||||
@ -169,12 +175,15 @@ import { timedEventManager } from "#app/global-event-manager";
|
||||
import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader";
|
||||
import { isVirtual, isIgnorePP, MoveUseMode } from "#enums/move-use-mode";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { LearnMoveSituation } from "#enums/learn-move-situation";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { AiType } from "#enums/ai-type";
|
||||
import type { MoveResult } from "#enums/move-result";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import type { IllusionData } from "#app/@types/illusion-data";
|
||||
import type { TurnMove } from "#app/@types/turn-move";
|
||||
import type { DamageCalculationResult, DamageResult } from "#app/@types/damage-result";
|
||||
import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#app/@types/ability-types";
|
||||
import { getTerrainBlockMessage } from "#app/data/terrain";
|
||||
import { LearnMoveSituation } from "#enums/learn-move-situation";
|
||||
|
||||
/** Base typeclass for damage parameter methods, used for DRY */
|
||||
type damageParams = {
|
||||
@ -4654,16 +4663,37 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
);
|
||||
}
|
||||
|
||||
queueImmuneMessage(quiet: boolean, effect?: StatusEffect): void {
|
||||
if (!effect || quiet) {
|
||||
/**
|
||||
* Display an immunity message for a failed status application.
|
||||
* @param quiet - Whether to suppress message and return early
|
||||
* @param reason - The reason for the status application failure -
|
||||
* can be "overlap" (already has same status), "other" (generic fail message)
|
||||
* or a {@linkcode TerrainType} for terrain-based blockages.
|
||||
* Defaults to "other".
|
||||
*/
|
||||
queueStatusImmuneMessage(
|
||||
quiet: boolean,
|
||||
reason: "overlap" | "other" | Exclude<TerrainType, TerrainType.NONE> = "other",
|
||||
): void {
|
||||
if (quiet) {
|
||||
return;
|
||||
}
|
||||
const message =
|
||||
effect && this.status?.effect === effect
|
||||
? getStatusEffectOverlapText(effect ?? StatusEffect.NONE, getPokemonNameWithAffix(this))
|
||||
: i18next.t("abilityTriggers:moveImmunity", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(this),
|
||||
});
|
||||
|
||||
let message: string;
|
||||
if (reason === "overlap") {
|
||||
// "XYZ is already XXX!"
|
||||
message = getStatusEffectOverlapText(this.status?.effect ?? StatusEffect.NONE, getPokemonNameWithAffix(this));
|
||||
} else if (typeof reason === "number") {
|
||||
// "XYZ was protected by the XXX terrain!" /
|
||||
// "XYZ surrounds itself with a protective mist!"
|
||||
message = getTerrainBlockMessage(this, reason);
|
||||
} else {
|
||||
// "It doesn't affect XXX!"
|
||||
message = i18next.t("abilityTriggers:moveImmunity", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(this),
|
||||
});
|
||||
}
|
||||
|
||||
globalScene.phaseManager.queueMessage(message);
|
||||
}
|
||||
|
||||
@ -4692,11 +4722,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
// Status-overriding moves (i.e. Rest) fail if their respective status already exists;
|
||||
// all other moves fail if the target already has _any_ status
|
||||
if (overrideStatus ? this.status?.effect === effect : this.status) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
this.queueStatusImmuneMessage(quiet, overrideStatus ? "overlap" : "other"); // having different status displays generic fail message
|
||||
return false;
|
||||
}
|
||||
if (this.isGrounded() && !ignoreField && globalScene.arena.terrain?.terrainType === TerrainType.MISTY) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
this.queueStatusImmuneMessage(quiet, TerrainType.MISTY);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -4705,6 +4735,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
/* Whether the target is immune to the specific status being applied. */
|
||||
let isImmune = false;
|
||||
/** The reason for a potential blockage; default "other" for type-based. */
|
||||
let reason: "other" | Exclude<TerrainType, TerrainType.NONE> = "other";
|
||||
|
||||
switch (effect) {
|
||||
case StatusEffect.POISON:
|
||||
@ -4716,6 +4748,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No source (such as from Toxic Spikes) = blocked by default
|
||||
if (!sourcePokemon) {
|
||||
return true;
|
||||
}
|
||||
@ -4735,10 +4768,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
isImmune = this.isOfType(PokemonType.ELECTRIC);
|
||||
break;
|
||||
case StatusEffect.SLEEP:
|
||||
isImmune = this.isGrounded() && globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC;
|
||||
isImmune = this.isGrounded() && globalScene.arena.getTerrainType() === TerrainType.ELECTRIC;
|
||||
reason = TerrainType.ELECTRIC;
|
||||
break;
|
||||
case StatusEffect.FREEZE: {
|
||||
const weatherType = globalScene.arena.weather?.weatherType;
|
||||
const weatherType = globalScene.arena.getWeatherType();
|
||||
isImmune =
|
||||
this.isOfType(PokemonType.ICE) ||
|
||||
(!ignoreField && (weatherType === WeatherType.SUNNY || weatherType === WeatherType.HARSH_SUN));
|
||||
@ -4750,7 +4784,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
if (isImmune) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
this.queueImmuneMessage(quiet, reason);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -6824,241 +6858,3 @@ export class EnemyPokemon extends Pokemon {
|
||||
this.battleInfo.toggleFlyout(visible);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Illusion property
|
||||
*/
|
||||
interface IllusionData {
|
||||
basePokemon: {
|
||||
/** The actual name of the Pokemon */
|
||||
name: string;
|
||||
/** The actual nickname of the Pokemon */
|
||||
nickname: string;
|
||||
/** Whether the base pokemon is shiny or not */
|
||||
shiny: boolean;
|
||||
/** The shiny variant of the base pokemon */
|
||||
variant: Variant;
|
||||
/** Whether the fusion species of the base pokemon is shiny or not */
|
||||
fusionShiny: boolean;
|
||||
/** The variant of the fusion species of the base pokemon */
|
||||
fusionVariant: Variant;
|
||||
};
|
||||
/** The species of the illusion */
|
||||
species: SpeciesId;
|
||||
/** The formIndex of the illusion */
|
||||
formIndex: number;
|
||||
/** The gender of the illusion */
|
||||
gender: Gender;
|
||||
/** The pokeball of the illusion */
|
||||
pokeball: PokeballType;
|
||||
/** The fusion species of the illusion if it's a fusion */
|
||||
fusionSpecies?: PokemonSpecies;
|
||||
/** The fusionFormIndex of the illusion */
|
||||
fusionFormIndex?: number;
|
||||
/** The fusionGender of the illusion if it's a fusion */
|
||||
fusionGender?: Gender;
|
||||
/** The level of the illusion (not used currently) */
|
||||
level?: number;
|
||||
}
|
||||
|
||||
export interface TurnMove {
|
||||
move: MoveId;
|
||||
targets: BattlerIndex[];
|
||||
useMode: MoveUseMode;
|
||||
result?: MoveResult;
|
||||
turn?: number;
|
||||
}
|
||||
|
||||
export interface AttackMoveResult {
|
||||
move: MoveId;
|
||||
result: DamageResult;
|
||||
damage: number;
|
||||
critical: boolean;
|
||||
sourceId: number;
|
||||
sourceBattlerIndex: BattlerIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistent in-battle data for a {@linkcode Pokemon}.
|
||||
* Resets on switch or new battle.
|
||||
*/
|
||||
export class PokemonSummonData {
|
||||
/** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */
|
||||
public statStages: number[] = [0, 0, 0, 0, 0, 0, 0];
|
||||
/**
|
||||
* A queue of moves yet to be executed, used by charging, recharging and frenzy moves.
|
||||
* So long as this array is nonempty, this Pokemon's corresponding `CommandPhase` will be skipped over entirely
|
||||
* in favor of using the queued move.
|
||||
* TODO: Clean up a lot of the code surrounding the move queue.
|
||||
*/
|
||||
public moveQueue: TurnMove[] = [];
|
||||
public tags: BattlerTag[] = [];
|
||||
public abilitySuppressed = false;
|
||||
|
||||
// Overrides for transform.
|
||||
// TODO: Move these into a separate class & add rage fist hit count
|
||||
public speciesForm: PokemonSpeciesForm | null = null;
|
||||
public fusionSpeciesForm: PokemonSpeciesForm | null = null;
|
||||
public ability: AbilityId | undefined;
|
||||
public passiveAbility: AbilityId | undefined;
|
||||
public gender: Gender | undefined;
|
||||
public fusionGender: Gender | undefined;
|
||||
public stats: number[] = [0, 0, 0, 0, 0, 0];
|
||||
public moveset: PokemonMove[] | null;
|
||||
|
||||
// If not initialized this value will not be populated from save data.
|
||||
public types: PokemonType[] = [];
|
||||
public addedType: PokemonType | null = null;
|
||||
|
||||
/** Data pertaining to this pokemon's illusion. */
|
||||
public illusion: IllusionData | null = null;
|
||||
public illusionBroken = false;
|
||||
|
||||
/** Array containing all berries eaten in the last turn; used by {@linkcode AbilityId.CUD_CHEW} */
|
||||
public berriesEatenLast: BerryType[] = [];
|
||||
|
||||
/**
|
||||
* An array of all moves this pokemon has used since entering the battle.
|
||||
* Used for most moves and abilities that check prior move usage or copy already-used moves.
|
||||
*/
|
||||
public moveHistory: TurnMove[] = [];
|
||||
|
||||
constructor(source?: PokemonSummonData | Partial<PokemonSummonData>) {
|
||||
if (isNullOrUndefined(source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Rework this into an actual generic function for use elsewhere
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
if (isNullOrUndefined(value) && this.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === "moveset") {
|
||||
this.moveset = value?.map((m: any) => PokemonMove.loadMove(m));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === "tags") {
|
||||
// load battler tags
|
||||
this.tags = value.map((t: BattlerTag) => loadBattlerTag(t));
|
||||
continue;
|
||||
}
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added
|
||||
export class PokemonTempSummonData {
|
||||
/**
|
||||
* The number of turns this pokemon has spent without switching out.
|
||||
* Only currently used for positioning the battle cursor.
|
||||
*/
|
||||
turnCount = 1;
|
||||
|
||||
/**
|
||||
* The number of turns this pokemon has spent in the active position since the start of the wave
|
||||
* without switching out.
|
||||
* Reset on switch and new wave, but not stored in `SummonData` to avoid being written to the save file.
|
||||
|
||||
* Used to evaluate "first turn only" conditions such as
|
||||
* {@linkcode MoveId.FAKE_OUT | Fake Out} and {@linkcode MoveId.FIRST_IMPRESSION | First Impression}).
|
||||
*/
|
||||
waveTurnCount = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistent data for a {@linkcode Pokemon}.
|
||||
* Resets at the start of a new battle (but not on switch).
|
||||
*/
|
||||
export class PokemonBattleData {
|
||||
/** Counter tracking direct hits this Pokemon has received during this battle; used for {@linkcode MoveId.RAGE_FIST} */
|
||||
public hitCount = 0;
|
||||
/** Whether this Pokemon has eaten a berry this battle; used for {@linkcode MoveId.BELCH} */
|
||||
public hasEatenBerry = false;
|
||||
/** Array containing all berries eaten and not yet recovered during this current battle; used by {@linkcode AbilityId.HARVEST} */
|
||||
public berriesEaten: BerryType[] = [];
|
||||
|
||||
constructor(source?: PokemonBattleData | Partial<PokemonBattleData>) {
|
||||
if (!isNullOrUndefined(source)) {
|
||||
this.hitCount = source.hitCount ?? 0;
|
||||
this.hasEatenBerry = source.hasEatenBerry ?? false;
|
||||
this.berriesEaten = source.berriesEaten ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary data for a {@linkcode Pokemon}.
|
||||
* Resets on new wave/battle start (but not on switch).
|
||||
*/
|
||||
export class PokemonWaveData {
|
||||
/** Whether the pokemon has endured due to a {@linkcode BattlerTagType.ENDURE_TOKEN} */
|
||||
public endured = false;
|
||||
/**
|
||||
* A set of all the abilities this {@linkcode Pokemon} has used in this wave.
|
||||
* Used to track once per battle conditions, as well as (hopefully) by the updated AI for move effectiveness.
|
||||
*/
|
||||
public abilitiesApplied: Set<AbilityId> = new Set<AbilityId>();
|
||||
/** Whether the pokemon's ability has been revealed or not */
|
||||
public abilityRevealed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary data for a {@linkcode Pokemon}.
|
||||
* Resets at the start of a new turn, as well as on switch.
|
||||
*/
|
||||
export class PokemonTurnData {
|
||||
public acted = false;
|
||||
/** How many times the current move should hit the target(s) */
|
||||
public hitCount = 0;
|
||||
/**
|
||||
* - `-1` = Calculate how many hits are left
|
||||
* - `0` = Move is finished
|
||||
*/
|
||||
public hitsLeft = -1;
|
||||
public totalDamageDealt = 0;
|
||||
public singleHitDamageDealt = 0;
|
||||
public damageTaken = 0;
|
||||
public attacksReceived: AttackMoveResult[] = [];
|
||||
public order: number;
|
||||
public statStagesIncreased = false;
|
||||
public statStagesDecreased = false;
|
||||
public moveEffectiveness: TypeDamageMultiplier | null = null;
|
||||
public combiningPledge?: MoveId;
|
||||
public switchedInThisTurn = false;
|
||||
public failedRunAway = false;
|
||||
public joinedRound = false;
|
||||
/**
|
||||
* The amount of times this Pokemon has acted again and used a move in the current turn.
|
||||
* Used to make sure multi-hits occur properly when the user is
|
||||
* forced to act again in the same turn, and **must be incremented** by any effects that grant extra actions.
|
||||
*/
|
||||
public extraTurns = 0;
|
||||
/**
|
||||
* All berries eaten by this pokemon in this turn.
|
||||
* Saved into {@linkcode PokemonSummonData | SummonData} by {@linkcode AbilityId.CUD_CHEW} on turn end.
|
||||
* @see {@linkcode PokemonSummonData.berriesEatenLast}
|
||||
*/
|
||||
public berriesEaten: BerryType[] = [];
|
||||
}
|
||||
|
||||
export type DamageResult =
|
||||
| HitResult.EFFECTIVE
|
||||
| HitResult.SUPER_EFFECTIVE
|
||||
| HitResult.NOT_VERY_EFFECTIVE
|
||||
| HitResult.ONE_HIT_KO
|
||||
| HitResult.CONFUSION
|
||||
| HitResult.INDIRECT_KO
|
||||
| HitResult.INDIRECT;
|
||||
|
||||
/** Interface containing the results of a damage calculation for a given move */
|
||||
export interface DamageCalculationResult {
|
||||
/** `true` if the move was cancelled (thus suppressing "No Effect" messages) */
|
||||
cancelled: boolean;
|
||||
/** The effectiveness of the move */
|
||||
result: HitResult;
|
||||
/** The damage dealt by the move */
|
||||
damage: number;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import CacheBustedLoaderPlugin from "#app/plugins/cache-busted-loader-plugin";
|
||||
import { SceneBase } from "#app/scene-base";
|
||||
import { WindowVariant, getWindowVariantSuffix } from "#app/ui/ui-theme";
|
||||
import { isMobile } from "#app/touch-controls";
|
||||
import { localPing, getEnumValues, hasAllLocalizedSprites, getEnumKeys } from "#app/utils/common";
|
||||
import { localPing, getEnumValues, hasAllLocalizedSprites } from "#app/utils/common";
|
||||
import { initPokemonPrevolutions, initPokemonStarters } from "#app/data/balance/pokemon-evolutions";
|
||||
import { initBiomes } from "#app/data/balance/biomes";
|
||||
import { initEggMoves } from "#app/data/balance/egg-moves";
|
||||
@ -270,7 +270,7 @@ export class LoadingScene extends SceneBase {
|
||||
this.loadAtlas("egg_icons", "egg");
|
||||
this.loadAtlas("egg_shard", "egg");
|
||||
this.loadAtlas("egg_lightrays", "egg");
|
||||
for (const gt of getEnumKeys(GachaType)) {
|
||||
for (const gt of Object.keys(GachaType)) {
|
||||
const key = gt.toLowerCase();
|
||||
this.loadImage(`gacha_${key}`, "egg");
|
||||
this.loadAtlas(`gacha_underlay_${key}`, "egg");
|
||||
|
@ -967,31 +967,23 @@ export class PokemonBaseStatTotalModifierType
|
||||
extends PokemonHeldItemModifierType
|
||||
implements GeneratedPersistentModifierType
|
||||
{
|
||||
private readonly statModifier: number;
|
||||
private readonly statModifier: 10 | -15;
|
||||
|
||||
constructor(statModifier: number) {
|
||||
constructor(statModifier: 10 | -15) {
|
||||
super(
|
||||
"modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE",
|
||||
"berry_juice",
|
||||
(_type, args) => new PokemonBaseStatTotalModifier(this, (args[0] as Pokemon).id, this.statModifier),
|
||||
statModifier > 0
|
||||
? "modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD"
|
||||
: "modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD",
|
||||
statModifier > 0 ? "berry_juice_good" : "berry_juice_bad",
|
||||
(_type, args) => new PokemonBaseStatTotalModifier(this, (args[0] as Pokemon).id, statModifier),
|
||||
);
|
||||
this.statModifier = statModifier;
|
||||
}
|
||||
|
||||
override getDescription(): string {
|
||||
return i18next.t("modifierType:ModifierType.PokemonBaseStatTotalModifierType.description", {
|
||||
increaseDecrease: i18next.t(
|
||||
this.statModifier >= 0
|
||||
? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.increase"
|
||||
: "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.decrease",
|
||||
),
|
||||
blessCurse: i18next.t(
|
||||
this.statModifier >= 0
|
||||
? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.blessed"
|
||||
: "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.cursed",
|
||||
),
|
||||
statValue: this.statModifier,
|
||||
});
|
||||
return this.statModifier > 0
|
||||
? i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD.description")
|
||||
: i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD.description");
|
||||
}
|
||||
|
||||
public getPregenArgs(): any[] {
|
||||
@ -999,38 +991,6 @@ export class PokemonBaseStatTotalModifierType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Old Gateau item
|
||||
*/
|
||||
export class PokemonBaseStatFlatModifierType
|
||||
extends PokemonHeldItemModifierType
|
||||
implements GeneratedPersistentModifierType
|
||||
{
|
||||
private readonly statModifier: number;
|
||||
private readonly stats: Stat[];
|
||||
|
||||
constructor(statModifier: number, stats: Stat[]) {
|
||||
super(
|
||||
"modifierType:ModifierType.MYSTERY_ENCOUNTER_OLD_GATEAU",
|
||||
"old_gateau",
|
||||
(_type, args) => new PokemonBaseStatFlatModifier(this, (args[0] as Pokemon).id, this.statModifier, this.stats),
|
||||
);
|
||||
this.statModifier = statModifier;
|
||||
this.stats = stats;
|
||||
}
|
||||
|
||||
override getDescription(): string {
|
||||
return i18next.t("modifierType:ModifierType.PokemonBaseStatFlatModifierType.description", {
|
||||
stats: this.stats.map(stat => i18next.t(getStatKey(stat))).join("/"),
|
||||
statValue: this.statModifier,
|
||||
});
|
||||
}
|
||||
|
||||
public getPregenArgs(): any[] {
|
||||
return [this.statModifier, this.stats];
|
||||
}
|
||||
}
|
||||
|
||||
class AllPokemonFullHpRestoreModifierType extends ModifierType {
|
||||
private descriptionKey: string;
|
||||
|
||||
@ -2331,17 +2291,16 @@ const modifierTypeInitObj = Object.freeze({
|
||||
MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () =>
|
||||
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs) {
|
||||
return new PokemonBaseStatTotalModifierType(pregenArgs[0] as number);
|
||||
return new PokemonBaseStatTotalModifierType(pregenArgs[0] as 10 | -15);
|
||||
}
|
||||
return new PokemonBaseStatTotalModifierType(randSeedInt(20, 1));
|
||||
return new PokemonBaseStatTotalModifierType(10);
|
||||
}),
|
||||
MYSTERY_ENCOUNTER_OLD_GATEAU: () =>
|
||||
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs) {
|
||||
return new PokemonBaseStatFlatModifierType(pregenArgs[0] as number, pregenArgs[1] as Stat[]);
|
||||
}
|
||||
return new PokemonBaseStatFlatModifierType(randSeedInt(20, 1), [Stat.HP, Stat.ATK, Stat.DEF]);
|
||||
}),
|
||||
new PokemonHeldItemModifierType(
|
||||
"modifierType:ModifierType.MYSTERY_ENCOUNTER_OLD_GATEAU",
|
||||
"old_gateau",
|
||||
(type, args) => new PokemonBaseStatFlatModifier(type, (args[0] as Pokemon).id),
|
||||
),
|
||||
MYSTERY_ENCOUNTER_BLACK_SLUDGE: () =>
|
||||
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs) {
|
||||
@ -2498,12 +2457,31 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod
|
||||
}
|
||||
|
||||
export interface CustomModifierSettings {
|
||||
/** If specified, will override the next X items to be the specified tier. These can upgrade with luck. */
|
||||
guaranteedModifierTiers?: ModifierTier[];
|
||||
/** If specified, will override the first X items to be specific modifier options (these should be pre-genned). */
|
||||
guaranteedModifierTypeOptions?: ModifierTypeOption[];
|
||||
/** If specified, will override the next X items to be auto-generated from specific modifier functions (these don't have to be pre-genned). */
|
||||
guaranteedModifierTypeFuncs?: ModifierTypeFunc[];
|
||||
/**
|
||||
* If set to `true`, will fill the remainder of shop items that were not overridden by the 3 options above, up to the `count` param value.
|
||||
* @example
|
||||
* ```ts
|
||||
* count = 4;
|
||||
* customModifierSettings = { guaranteedModifierTiers: [ModifierTier.GREAT], fillRemaining: true };
|
||||
* ```
|
||||
* The first item in the shop will be `GREAT` tier, and the remaining `3` items will be generated normally.
|
||||
*
|
||||
* If `fillRemaining: false` in the same scenario, only 1 `GREAT` tier item will appear in the shop (regardless of the value of `count`).
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
fillRemaining?: boolean;
|
||||
/** Set to negative value to disable rerolls completely in shop */
|
||||
/** If specified, can adjust the amount of money required for a shop reroll. If set to a negative value, the shop will not allow rerolls at all. */
|
||||
rerollMultiplier?: number;
|
||||
/**
|
||||
* If `false`, will prevent set item tiers from upgrading via luck.
|
||||
* @defaultValue `true`
|
||||
*/
|
||||
allowLuckUpgrades?: boolean;
|
||||
}
|
||||
|
||||
@ -2513,19 +2491,10 @@ export function getModifierTypeFuncById(id: string): ModifierTypeFunc {
|
||||
|
||||
/**
|
||||
* Generates modifier options for a {@linkcode SelectModifierPhase}
|
||||
* @param count Determines the number of items to generate
|
||||
* @param party Party is required for generating proper modifier pools
|
||||
* @param modifierTiers (Optional) If specified, rolls items in the specified tiers. Commonly used for tier-locking with Lock Capsule.
|
||||
* @param customModifierSettings (Optional) If specified, can customize the item shop rewards further.
|
||||
* - `guaranteedModifierTypeOptions?: ModifierTypeOption[]` If specified, will override the first X items to be specific modifier options (these should be pre-genned).
|
||||
* - `guaranteedModifierTypeFuncs?: ModifierTypeFunc[]` If specified, will override the next X items to be auto-generated from specific modifier functions (these don't have to be pre-genned).
|
||||
* - `guaranteedModifierTiers?: ModifierTier[]` If specified, will override the next X items to be the specified tier. These can upgrade with luck.
|
||||
* - `fillRemaining?: boolean` Default 'false'. If set to true, will fill the remainder of shop items that were not overridden by the 3 options above, up to the 'count' param value.
|
||||
* - Example: `count = 4`, `customModifierSettings = { guaranteedModifierTiers: [ModifierTier.GREAT], fillRemaining: true }`,
|
||||
* - The first item in the shop will be `GREAT` tier, and the remaining 3 items will be generated normally.
|
||||
* - If `fillRemaining = false` in the same scenario, only 1 `GREAT` tier item will appear in the shop (regardless of `count` value).
|
||||
* - `rerollMultiplier?: number` If specified, can adjust the amount of money required for a shop reroll. If set to a negative value, the shop will not allow rerolls at all.
|
||||
* - `allowLuckUpgrades?: boolean` Default `true`, if `false` will prevent set item tiers from upgrading via luck
|
||||
* @param count - Determines the number of items to generate
|
||||
* @param party - Party is required for generating proper modifier pools
|
||||
* @param modifierTiers - (Optional) If specified, rolls items in the specified tiers. Commonly used for tier-locking with Lock Capsule.
|
||||
* @param customModifierSettings - See {@linkcode CustomModifierSettings}
|
||||
*/
|
||||
export function getPlayerModifierTypeOptions(
|
||||
count: number,
|
||||
@ -2536,16 +2505,10 @@ export function getPlayerModifierTypeOptions(
|
||||
const options: ModifierTypeOption[] = [];
|
||||
const retryCount = Math.min(count * 5, 50);
|
||||
if (!customModifierSettings) {
|
||||
new Array(count).fill(0).map((_, i) => {
|
||||
options.push(
|
||||
getModifierTypeOptionWithRetry(
|
||||
options,
|
||||
retryCount,
|
||||
party,
|
||||
modifierTiers && modifierTiers.length > i ? modifierTiers[i] : undefined,
|
||||
),
|
||||
);
|
||||
});
|
||||
for (let i = 0; i < count; i++) {
|
||||
const tier = modifierTiers && modifierTiers.length > i ? modifierTiers[i] : undefined;
|
||||
options.push(getModifierTypeOptionWithRetry(options, retryCount, party, tier));
|
||||
}
|
||||
} else {
|
||||
// Guaranteed mod options first
|
||||
if (
|
||||
|
@ -952,10 +952,9 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier {
|
||||
export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
public override type: PokemonBaseStatTotalModifierType;
|
||||
public isTransferable = false;
|
||||
public statModifier: 10 | -15;
|
||||
|
||||
private statModifier: number;
|
||||
|
||||
constructor(type: PokemonBaseStatTotalModifierType, pokemonId: number, statModifier: number, stackCount?: number) {
|
||||
constructor(type: PokemonBaseStatTotalModifierType, pokemonId: number, statModifier: 10 | -15, stackCount?: number) {
|
||||
super(type, pokemonId, stackCount);
|
||||
this.statModifier = statModifier;
|
||||
}
|
||||
@ -1012,31 +1011,14 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
* Currently used by Old Gateau item
|
||||
*/
|
||||
export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
||||
private statModifier: number;
|
||||
private stats: Stat[];
|
||||
public isTransferable = false;
|
||||
|
||||
constructor(type: ModifierType, pokemonId: number, statModifier: number, stats: Stat[], stackCount?: number) {
|
||||
super(type, pokemonId, stackCount);
|
||||
|
||||
this.statModifier = statModifier;
|
||||
this.stats = stats;
|
||||
}
|
||||
|
||||
override matchType(modifier: Modifier): boolean {
|
||||
return (
|
||||
modifier instanceof PokemonBaseStatFlatModifier &&
|
||||
modifier.statModifier === this.statModifier &&
|
||||
this.stats.every(s => modifier.stats.some(stat => s === stat))
|
||||
);
|
||||
return modifier instanceof PokemonBaseStatFlatModifier;
|
||||
}
|
||||
|
||||
override clone(): PersistentModifier {
|
||||
return new PokemonBaseStatFlatModifier(this.type, this.pokemonId, this.statModifier, this.stats, this.stackCount);
|
||||
}
|
||||
|
||||
override getArgs(): any[] {
|
||||
return [...super.getArgs(), this.statModifier, this.stats];
|
||||
return new PokemonBaseStatFlatModifier(this.type, this.pokemonId, this.stackCount);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1055,11 +1037,13 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
||||
* @param baseStats The base stats of the {@linkcode Pokemon}
|
||||
* @returns always `true`
|
||||
*/
|
||||
override apply(_pokemon: Pokemon, baseStats: number[]): boolean {
|
||||
override apply(pokemon: Pokemon, baseStats: number[]): boolean {
|
||||
// Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats
|
||||
const stats = this.getStats(pokemon);
|
||||
const statModifier = 20;
|
||||
baseStats.forEach((v, i) => {
|
||||
if (this.stats.includes(i)) {
|
||||
const newVal = Math.floor(v + this.statModifier);
|
||||
if (stats.includes(i)) {
|
||||
const newVal = Math.floor(v + statModifier);
|
||||
baseStats[i] = Math.min(Math.max(newVal, 1), 999999);
|
||||
}
|
||||
});
|
||||
@ -1067,6 +1051,22 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef
|
||||
* @returns Array of 3 {@linkcode Stat}s to boost
|
||||
*/
|
||||
getStats(pokemon: Pokemon): Stat[] {
|
||||
const stats: Stat[] = [];
|
||||
const baseStats = pokemon.getSpeciesForm().baseStats.slice(0);
|
||||
// HP or Speed
|
||||
stats.push(baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD);
|
||||
// Attack or SpAtk
|
||||
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
|
||||
// Def or SpDef
|
||||
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
|
||||
return stats;
|
||||
}
|
||||
|
||||
override getScoreMultiplier(): number {
|
||||
return 1.1;
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { type PokeballCounts } from "#app/battle-scene";
|
||||
import { EvolutionItem } from "#app/data/balance/pokemon-evolutions";
|
||||
import { Gender } from "#app/data/gender";
|
||||
import { FormChangeItem } from "#enums/form-change-item";
|
||||
import { type ModifierOverride } from "#app/modifier/modifier-type";
|
||||
import { Variant } from "#app/sprites/variant";
|
||||
import { Unlockables } from "#enums/unlockables";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { EggTier } from "#enums/egg-type";
|
||||
import { FormChangeItem } from "#enums/form-change-item";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
@ -20,6 +20,7 @@ import { Stat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { TimeOfDay } from "#enums/time-of-day";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { Unlockables } from "#enums/unlockables";
|
||||
import { VariantTier } from "#enums/variant-tier";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
|
||||
@ -118,7 +119,13 @@ class DefaultOverrides {
|
||||
* or `false` to force it to never trigger.
|
||||
*/
|
||||
readonly CONFUSION_ACTIVATION_OVERRIDE: boolean | null = null;
|
||||
|
||||
/**
|
||||
* If non-null, will override random flee attempts to always or never succeed by forcing {@linkcode calculateEscapeChance} to return 100% or 0%.
|
||||
* Set to `null` to disable.
|
||||
*
|
||||
* Is overridden if either player Pokemon has {@linkcode AbilityId.RUN_AWAY | Run Away}.
|
||||
*/
|
||||
readonly RUN_SUCCESS_OVERRIDE: boolean | null = null;
|
||||
// ----------------
|
||||
// PLAYER OVERRIDES
|
||||
// ----------------
|
||||
@ -159,10 +166,20 @@ class DefaultOverrides {
|
||||
readonly MOVESET_OVERRIDE: MoveId | Array<MoveId> = [];
|
||||
readonly SHINY_OVERRIDE: boolean | null = null;
|
||||
readonly VARIANT_OVERRIDE: Variant | null = null;
|
||||
/**
|
||||
* Overrides the IVs of player pokemon. Values must never be outside the range `0` to `31`!
|
||||
* - If set to a number between `0` and `31`, set all IVs of all player pokemon to that number.
|
||||
* - If set to an array, set the IVs of all player pokemon to that array. Array length must be exactly `6`!
|
||||
* - If set to `null`, disable the override.
|
||||
*/
|
||||
readonly IVS_OVERRIDE: number | number[] | null = null;
|
||||
/** Override the nature of all player pokemon to the specified nature. Disabled if `null`. */
|
||||
readonly NATURE_OVERRIDE: Nature | null = null;
|
||||
|
||||
// --------------------------
|
||||
// OPPONENT / ENEMY OVERRIDES
|
||||
// --------------------------
|
||||
// TODO: rename `OPP_` to `ENEMY_`
|
||||
readonly OPP_SPECIES_OVERRIDE: SpeciesId | number = 0;
|
||||
/**
|
||||
* This will make all opponents fused Pokemon
|
||||
@ -181,7 +198,15 @@ class DefaultOverrides {
|
||||
readonly OPP_MOVESET_OVERRIDE: MoveId | Array<MoveId> = [];
|
||||
readonly OPP_SHINY_OVERRIDE: boolean | null = null;
|
||||
readonly OPP_VARIANT_OVERRIDE: Variant | null = null;
|
||||
readonly OPP_IVS_OVERRIDE: number | number[] = [];
|
||||
/**
|
||||
* Overrides the IVs of enemy pokemon. Values must never be outside the range `0` to `31`!
|
||||
* - If set to a number between `0` and `31`, set all IVs of all enemy pokemon to that number.
|
||||
* - If set to an array, set the IVs of all enemy pokemon to that array. Array length must be exactly `6`!
|
||||
* - If set to `null`, disable the override.
|
||||
*/
|
||||
readonly ENEMY_IVS_OVERRIDE: number | number[] | null = null;
|
||||
/** Override the nature of all enemy pokemon to the specified nature. Disabled if `null`. */
|
||||
readonly ENEMY_NATURE_OVERRIDE: Nature | null = null;
|
||||
readonly OPP_FORM_OVERRIDES: Partial<Record<SpeciesId, number>> = {};
|
||||
/**
|
||||
* Override to give the enemy Pokemon a given amount of health segments
|
||||
|
@ -1,34 +1,33 @@
|
||||
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import Overrides from "#app/overrides";
|
||||
import { FieldPhase } from "#app/phases/field-phase";
|
||||
import { NumberHolder } from "#app/utils/common";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import i18next from "i18next";
|
||||
import { NumberHolder } from "#app/utils/common";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
export class AttemptRunPhase extends PokemonPhase {
|
||||
export class AttemptRunPhase extends FieldPhase {
|
||||
public readonly phaseName = "AttemptRunPhase";
|
||||
/** For testing purposes: this is to force the pokemon to fail and escape */
|
||||
public forceFailEscape = false;
|
||||
|
||||
start() {
|
||||
public start() {
|
||||
super.start();
|
||||
|
||||
const playerField = globalScene.getPlayerField();
|
||||
// Increment escape attempts count on entry
|
||||
const currentAttempts = globalScene.currentBattle.escapeAttempts++;
|
||||
|
||||
const activePlayerField = globalScene.getPlayerField(true);
|
||||
const enemyField = globalScene.getEnemyField();
|
||||
|
||||
const playerPokemon = this.getPokemon();
|
||||
const escapeRoll = globalScene.randBattleSeedInt(100);
|
||||
const escapeChance = new NumberHolder(this.calculateEscapeChance(currentAttempts));
|
||||
|
||||
const escapeChance = new NumberHolder(0);
|
||||
activePlayerField.forEach(pokemon => {
|
||||
applyAbAttrs("RunSuccessAbAttr", { pokemon, chance: escapeChance });
|
||||
});
|
||||
|
||||
this.attemptRunAway(playerField, enemyField, escapeChance);
|
||||
|
||||
applyAbAttrs("RunSuccessAbAttr", { pokemon: playerPokemon, chance: escapeChance });
|
||||
|
||||
if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
|
||||
enemyField.forEach(enemyPokemon => applyAbAttrs("PreLeaveFieldAbAttr", { pokemon: enemyPokemon }));
|
||||
if (escapeRoll < escapeChance.value) {
|
||||
enemyField.forEach(pokemon => applyAbAttrs("PreLeaveFieldAbAttr", { pokemon }));
|
||||
|
||||
globalScene.playSound("se/flee");
|
||||
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
||||
@ -57,24 +56,35 @@ export class AttemptRunPhase extends PokemonPhase {
|
||||
|
||||
globalScene.phaseManager.pushNew("NewBattlePhase");
|
||||
} else {
|
||||
playerPokemon.turnData.failedRunAway = true;
|
||||
activePlayerField.forEach(p => {
|
||||
p.turnData.failedRunAway = true;
|
||||
});
|
||||
|
||||
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500);
|
||||
}
|
||||
|
||||
this.end();
|
||||
}
|
||||
|
||||
attemptRunAway(playerField: PlayerPokemon[], enemyField: EnemyPokemon[], escapeChance: NumberHolder) {
|
||||
/** Sum of the speed of all enemy pokemon on the field */
|
||||
const enemySpeed = enemyField.reduce(
|
||||
(total: number, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD),
|
||||
0,
|
||||
);
|
||||
/** Sum of the speed of all player pokemon on the field */
|
||||
const playerSpeed = playerField.reduce(
|
||||
(total: number, playerPokemon: Pokemon) => total + playerPokemon.getStat(Stat.SPD),
|
||||
0,
|
||||
);
|
||||
/**
|
||||
* Calculate the chance for the player's team to successfully run away from battle.
|
||||
*
|
||||
* @param escapeAttempts - The number of prior failed escape attempts in the current battle
|
||||
* @returns The final escape chance, as percentage out of 100.
|
||||
*/
|
||||
public calculateEscapeChance(escapeAttempts: number): number {
|
||||
// Check for override, guaranteeing or forbidding random flee attempts as applicable.
|
||||
if (Overrides.RUN_SUCCESS_OVERRIDE !== null) {
|
||||
return Overrides.RUN_SUCCESS_OVERRIDE ? 100 : 0;
|
||||
}
|
||||
|
||||
const enemyField = globalScene.getEnemyField();
|
||||
const activePlayerField = globalScene.getPlayerField(true);
|
||||
|
||||
// Cf https://bulbapedia.bulbagarden.net/wiki/Escape#Generation_V_onwards
|
||||
// From gen 5 onwards, running takes the _base_ speed totals of both party sides.
|
||||
const enemySpeed = enemyField.reduce((total, enemy) => total + enemy.getStat(Stat.SPD), 0);
|
||||
const playerSpeed = activePlayerField.reduce((total, player) => total + player.getStat(Stat.SPD), 0);
|
||||
|
||||
/* The way the escape chance works is by looking at the difference between your speed and the enemy field's average speed as a ratio. The higher this ratio, the higher your chance of success.
|
||||
* However, there is a cap for the ratio of your speed vs enemy speed which beyond that point, you won't gain any advantage. It also looks at how many times you've tried to escape.
|
||||
@ -92,10 +102,8 @@ export class AttemptRunPhase extends PokemonPhase {
|
||||
* From the above, we can calculate the below values
|
||||
*/
|
||||
|
||||
let isBoss = false;
|
||||
for (let e = 0; e < enemyField.length; e++) {
|
||||
isBoss = isBoss || enemyField[e].isBoss(); // this line checks if any of the enemy pokemon on the field are bosses; if so, the calculation for escaping is different
|
||||
}
|
||||
/** Whether at least 1 pokemon on the enemy field is a boss. */
|
||||
const isBoss = enemyField.some(e => e.isBoss());
|
||||
|
||||
/** The ratio between the speed of your active pokemon and the speed of the enemy field */
|
||||
const speedRatio = playerSpeed / enemySpeed;
|
||||
@ -111,8 +119,8 @@ export class AttemptRunPhase extends PokemonPhase {
|
||||
const escapeSlope = (maxChance - minChance) / speedCap;
|
||||
|
||||
// This will calculate the escape chance given all of the above and clamp it to the range of [`minChance`, `maxChance`]
|
||||
escapeChance.value = Phaser.Math.Clamp(
|
||||
Math.round(escapeSlope * speedRatio + minChance + escapeBonus * globalScene.currentBattle.escapeAttempts++),
|
||||
return Phaser.Math.Clamp(
|
||||
Math.round(escapeSlope * speedRatio + minChance + escapeBonus * escapeAttempts),
|
||||
minChance,
|
||||
maxChance,
|
||||
);
|
||||
|
@ -11,7 +11,8 @@ import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import type { PlayerPokemon, TurnMove } from "#app/field/pokemon";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import type { TurnMove } from "#app/@types/turn-move";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { Command } from "#enums/command";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattleSpec } from "#enums/battle-spec";
|
||||
import type { DamageResult } from "#app/field/pokemon";
|
||||
import type { DamageResult } from "#app/@types/damage-result";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { fixedInt } from "#app/utils/common";
|
||||
import { PokemonPhase } from "#app/phases/pokemon-phase";
|
||||
|
@ -388,7 +388,7 @@ export class EvolutionPhase extends Phase {
|
||||
globalScene.ui.showText(
|
||||
i18next.t("menu:evolutionDone", {
|
||||
pokemonName: this.preEvolvedPokemonName,
|
||||
evolvedPokemonName: this.pokemon.species.getExpandedSpeciesName(),
|
||||
evolvedPokemonName: this.pokemon.name,
|
||||
}),
|
||||
null,
|
||||
() => this.end(),
|
||||
|
@ -21,7 +21,8 @@ import { MoveTarget } from "#enums/MoveTarget";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import type { DamageResult, TurnMove } from "#app/field/pokemon";
|
||||
import type { DamageResult } from "#app/@types/damage-result";
|
||||
import type { TurnMove } from "#app/@types/turn-move";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
@ -431,9 +432,15 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
||||
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
||||
* @param wasCritical - `true` if the move was a critical hit
|
||||
*/
|
||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
|
||||
applyAbAttrs("PostDefendAbAttr", { pokemon: target, opponent: user, move: this.move, hitResult });
|
||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult, wasCritical = false): void {
|
||||
const params = { pokemon: target, opponent: user, move: this.move, hitResult };
|
||||
applyAbAttrs("PostDefendAbAttr", params);
|
||||
|
||||
if (wasCritical) {
|
||||
applyAbAttrs("PostReceiveCritStatStageChangeAbAttr", params);
|
||||
}
|
||||
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
||||
}
|
||||
|
||||
@ -787,12 +794,12 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
|
||||
this.triggerMoveEffects(MoveEffectTrigger.PRE_APPLY, user, target);
|
||||
|
||||
const hitResult = this.applyMove(user, target, effectiveness);
|
||||
const [hitResult, wasCritical] = this.applyMove(user, target, effectiveness);
|
||||
|
||||
// Apply effects to the user (always) and the target (if not blocked by substitute).
|
||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, true);
|
||||
if (!this.move.hitsSubstitute(user, target)) {
|
||||
this.applyOnTargetEffects(user, target, hitResult, firstTarget);
|
||||
this.applyOnTargetEffects(user, target, hitResult, firstTarget, wasCritical);
|
||||
}
|
||||
if (this.lastHit) {
|
||||
globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
||||
@ -812,8 +819,9 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||
* @param effectiveness - The effectiveness of the move against the target
|
||||
* @returns The {@linkcode HitResult} of the move against the target and a boolean indicating whether the target was crit
|
||||
*/
|
||||
protected applyMoveDamage(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): HitResult {
|
||||
protected applyMoveDamage(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): [HitResult, boolean] {
|
||||
const isCritical = target.getCriticalHitResult(user, this.move);
|
||||
|
||||
/*
|
||||
@ -844,7 +852,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
const isOneHitKo = result === HitResult.ONE_HIT_KO;
|
||||
|
||||
if (!dmg) {
|
||||
return result;
|
||||
return [result, false];
|
||||
}
|
||||
|
||||
target.lapseTags(BattlerTagLapseType.HIT);
|
||||
@ -872,7 +880,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
}
|
||||
|
||||
if (damage <= 0) {
|
||||
return result;
|
||||
return [result, isCritical];
|
||||
}
|
||||
|
||||
if (user.isPlayer()) {
|
||||
@ -901,7 +909,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage));
|
||||
}
|
||||
|
||||
return result;
|
||||
return [result, isCritical];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -955,17 +963,17 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param target - The {@linkcode Pokemon} struck by the move
|
||||
* @param effectiveness - The effectiveness of the move against the target
|
||||
*/
|
||||
protected applyMove(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): HitResult {
|
||||
protected applyMove(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): [HitResult, boolean] {
|
||||
const moveCategory = user.getMoveCategory(target, this.move);
|
||||
|
||||
if (moveCategory === MoveCategory.STATUS) {
|
||||
return HitResult.STATUS;
|
||||
return [HitResult.STATUS, false];
|
||||
}
|
||||
|
||||
const result = this.applyMoveDamage(user, target, effectiveness);
|
||||
|
||||
if (user.turnData.hitsLeft === 1 || target.isFainted()) {
|
||||
this.queueHitResultMessage(result);
|
||||
this.queueHitResultMessage(result[0]);
|
||||
}
|
||||
|
||||
if (target.isFainted()) {
|
||||
@ -982,8 +990,15 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||
* @param hitResult - The {@linkcode HitResult} obtained from applying the move
|
||||
* @param firstTarget - `true` if the target is the first Pokemon hit by the attack
|
||||
* @param wasCritical - `true` if the move was a critical hit
|
||||
*/
|
||||
protected applyOnTargetEffects(user: Pokemon, target: Pokemon, hitResult: HitResult, firstTarget: boolean): void {
|
||||
protected applyOnTargetEffects(
|
||||
user: Pokemon,
|
||||
target: Pokemon,
|
||||
hitResult: HitResult,
|
||||
firstTarget: boolean,
|
||||
wasCritical = false,
|
||||
): void {
|
||||
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
||||
const dealsDamage = [
|
||||
HitResult.EFFECTIVE,
|
||||
@ -994,7 +1009,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
|
||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
||||
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
||||
this.applyOnGetHitAbEffects(user, target, hitResult);
|
||||
this.applyOnGetHitAbEffects(user, target, hitResult, wasCritical);
|
||||
applyAbAttrs("PostAttackAbAttr", { pokemon: user, opponent: target, move: this.move, hitResult });
|
||||
|
||||
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
|
||||
|
@ -11,7 +11,7 @@ import { MoveFlags } from "#enums/MoveFlags";
|
||||
import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||
import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { getTerrainBlockMessage, getWeatherBlockMessage } from "#app/data/weather";
|
||||
import { getWeatherBlockMessage } from "#app/data/weather";
|
||||
import { MoveUsedEvent } from "#app/events/battle-scene";
|
||||
import type { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
@ -26,6 +26,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import i18next from "i18next";
|
||||
import { getTerrainBlockMessage } from "#app/data/terrain";
|
||||
import { isVirtual, isIgnorePP, isReflected, MoveUseMode, isIgnoreStatus } from "#enums/move-use-mode";
|
||||
import { frenzyMissFunc } from "#app/data/moves/move-utils";
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||
import { allMoves } from "#app/data/data-lists";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
@ -213,27 +212,8 @@ export class TurnStartPhase extends FieldPhase {
|
||||
break;
|
||||
case Command.RUN:
|
||||
{
|
||||
let runningPokemon = pokemon;
|
||||
if (globalScene.currentBattle.double) {
|
||||
const playerActivePokemon = field.filter(pokemon => {
|
||||
if (pokemon) {
|
||||
return pokemon.isPlayer() && pokemon.isActive();
|
||||
}
|
||||
return;
|
||||
});
|
||||
// if only one pokemon is alive, use that one
|
||||
if (playerActivePokemon.length > 1) {
|
||||
// find which active pokemon has faster speed
|
||||
const fasterPokemon =
|
||||
playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD)
|
||||
? playerActivePokemon[0]
|
||||
: playerActivePokemon[1];
|
||||
// check if either active pokemon has the ability "Run Away"
|
||||
const hasRunAway = playerActivePokemon.find(p => p.hasAbility(AbilityId.RUN_AWAY));
|
||||
runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon;
|
||||
}
|
||||
}
|
||||
phaseManager.unshiftNew("AttemptRunPhase", runningPokemon.getFieldIndex());
|
||||
// Running (like ball throwing) is a team action taking up both Pokemon's turns.
|
||||
phaseManager.unshiftNew("AttemptRunPhase");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -65,28 +65,27 @@ const fonts: Array<LoadingFontFaceProperty> = [
|
||||
unicodeRange: rangesByLanguage.chinese,
|
||||
}),
|
||||
extraOptions: { sizeAdjust: "70%", format: "woff2" },
|
||||
only: ["en", "es", "fr", "it", "de", "zh", "pt", "ko", "ca", "da", "tr", "ro", "ru"],
|
||||
only: ["zh"],
|
||||
},
|
||||
{
|
||||
face: new FontFace("pkmnems", "url(./fonts/unifont-15.1.05.subset.woff2)", {
|
||||
unicodeRange: rangesByLanguage.chinese,
|
||||
}),
|
||||
extraOptions: { format: "woff2" },
|
||||
only: ["en", "es", "fr", "it", "de", "zh", "pt", "ko", "ca", "da", "tr", "ro", "ru"],
|
||||
only: ["zh"],
|
||||
},
|
||||
// japanese
|
||||
{
|
||||
face: new FontFace("emerald", "url(./fonts/Galmuri11.subset.woff2)", {
|
||||
face: new FontFace("emerald", "url(./fonts/pokemon-bw.ttf)", {
|
||||
unicodeRange: rangesByLanguage.japanese,
|
||||
}),
|
||||
extraOptions: { sizeAdjust: "66%" },
|
||||
only: ["ja"],
|
||||
only: ["en", "es", "fr", "it", "de", "pt", "ko", "ja", "ca", "da", "tr", "ro", "ru"],
|
||||
},
|
||||
{
|
||||
face: new FontFace("pkmnems", "url(./fonts/Galmuri9.subset.woff2)", {
|
||||
face: new FontFace("pkmnems", "url(./fonts/pokemon-bw.ttf)", {
|
||||
unicodeRange: rangesByLanguage.japanese,
|
||||
}),
|
||||
only: ["ja"],
|
||||
only: ["en", "es", "fr", "it", "de", "pt", "ko", "ja", "ca", "da", "tr", "ro", "ru"],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -6,14 +6,14 @@ import { PokeballType } from "#enums/pokeball";
|
||||
import { getPokemonSpeciesForm } from "#app/data/pokemon-species";
|
||||
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
|
||||
import { Status } from "../data/status-effect";
|
||||
import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonSummonData } from "../field/pokemon";
|
||||
import Pokemon, { EnemyPokemon } from "../field/pokemon";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import type { Variant } from "#app/sprites/variant";
|
||||
import type { BiomeId } from "#enums/biome-id";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import { CustomPokemonData, PokemonBattleData, PokemonSummonData } from "#app/data/pokemon/pokemon-data";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
|
||||
export default class PokemonData {
|
||||
@ -105,7 +105,7 @@ export default class PokemonData {
|
||||
|
||||
// TODO: Can't we move some of this verification stuff to an upgrade script?
|
||||
this.nature = source.nature ?? Nature.HARDY;
|
||||
this.moveset = source.moveset.map((m: any) => PokemonMove.loadMove(m));
|
||||
this.moveset = source.moveset?.map((m: any) => PokemonMove.loadMove(m)) ?? [];
|
||||
this.status = source.status
|
||||
? new Status(source.status.effect, source.status.toxicTurnCount, source.status.sleepTurnsRemaining)
|
||||
: null;
|
||||
|
@ -11,25 +11,22 @@ import { PlayerGender } from "#enums/player-gender";
|
||||
import { ShopCursorTarget } from "#enums/shop-cursor-target";
|
||||
import { isLocal } from "#app/utils/common";
|
||||
|
||||
const VOLUME_OPTIONS: SettingOption[] = new Array(11).fill(null).map((_, i) =>
|
||||
i
|
||||
? {
|
||||
value: (i * 10).toString(),
|
||||
label: (i * 10).toString(),
|
||||
}
|
||||
: {
|
||||
value: "Mute",
|
||||
label: i18next.t("settings:mute"),
|
||||
},
|
||||
);
|
||||
const VOLUME_OPTIONS: SettingOption[] = [
|
||||
{
|
||||
value: "Mute",
|
||||
label: i18next.t("settings:mute"),
|
||||
},
|
||||
];
|
||||
for (let i = 1; i < 11; i++) {
|
||||
const value = (i * 10).toString();
|
||||
VOLUME_OPTIONS.push({ value, label: value });
|
||||
}
|
||||
|
||||
const SHOP_OVERLAY_OPACITY_OPTIONS: SettingOption[] = new Array(9).fill(null).map((_, i) => {
|
||||
const SHOP_OVERLAY_OPACITY_OPTIONS: SettingOption[] = [];
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const value = ((i + 1) * 10).toString();
|
||||
return {
|
||||
value,
|
||||
label: value,
|
||||
};
|
||||
});
|
||||
SHOP_OVERLAY_OPACITY_OPTIONS.push({ value, label: value });
|
||||
}
|
||||
|
||||
const OFF_ON: SettingOption[] = [
|
||||
{
|
||||
@ -183,6 +180,12 @@ export enum MusicPreference {
|
||||
ALLGENS,
|
||||
}
|
||||
|
||||
const windowTypeOptions: SettingOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const value = (i + 1).toString();
|
||||
windowTypeOptions.push({ value, label: value });
|
||||
}
|
||||
|
||||
/**
|
||||
* All Settings not related to controls
|
||||
*/
|
||||
@ -432,13 +435,7 @@ export const Setting: Array<Setting> = [
|
||||
{
|
||||
key: SettingKeys.Window_Type,
|
||||
label: i18next.t("settings:windowType"),
|
||||
options: new Array(5).fill(null).map((_, i) => {
|
||||
const windowType = (i + 1).toString();
|
||||
return {
|
||||
value: windowType,
|
||||
label: windowType,
|
||||
};
|
||||
}),
|
||||
options: windowTypeOptions,
|
||||
default: 0,
|
||||
type: SettingType.DISPLAY,
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ import { defaultStarterSpecies } from "#app/constants";
|
||||
import { AbilityAttr } from "#enums/ability-attr";
|
||||
import { DexAttr } from "#enums/dex-attr";
|
||||
import { allSpecies } from "#app/data/data-lists";
|
||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import { CustomPokemonData } from "#app/data/pokemon/pokemon-data";
|
||||
import { isNullOrUndefined } from "#app/utils/common";
|
||||
import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator";
|
||||
import type { SettingsSaveMigrator } from "#app/@types/SettingsSaveMigrator";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import type { TurnMove } from "#app/field/pokemon";
|
||||
import type { TurnMove } from "#app/@types/turn-move";
|
||||
import type { MoveResult } from "#enums/move-result";
|
||||
import type { SessionSaveData } from "#app/system/game-data";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
@ -81,7 +81,7 @@ export default class AchvsUiHandler extends MessageUiHandler {
|
||||
|
||||
this.headerBg = addWindow(0, 0, WIDTH - 2, 24);
|
||||
|
||||
this.headerText = addTextObject(0, 0, "", TextStyle.SETTINGS_LABEL)
|
||||
this.headerText = addTextObject(0, 0, "", TextStyle.HEADER_LABEL)
|
||||
.setOrigin(0)
|
||||
.setPositionRelative(this.headerBg, 8, 4);
|
||||
this.headerActionButton = new Phaser.GameObjects.Sprite(globalScene, 0, 0, "keyboard", "ACTION.png")
|
||||
|
@ -115,7 +115,7 @@ export default class BallUiHandler extends UiHandler {
|
||||
updateCounts() {
|
||||
this.countsText.setText(
|
||||
Object.values(globalScene.pokeballCounts)
|
||||
.map(c => `x${c}`)
|
||||
.map(c => `×${c}`)
|
||||
.join("\n"),
|
||||
);
|
||||
}
|
||||
|
@ -96,7 +96,6 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
|
||||
const levelUpStatsLabelsContent = addTextObject(globalScene.game.canvas.width / 6 - 73, -94, "", TextStyle.WINDOW, {
|
||||
maxLines: 6,
|
||||
});
|
||||
levelUpStatsLabelsContent.setLineSpacing(i18next.resolvedLanguage === "ja" ? 25 : 5);
|
||||
let levelUpStatsLabelText = "";
|
||||
|
||||
for (const s of PERMANENT_STATS) {
|
||||
@ -123,7 +122,6 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
|
||||
TextStyle.WINDOW,
|
||||
{ maxLines: 6 },
|
||||
);
|
||||
levelUpStatsIncrContent.setLineSpacing(i18next.resolvedLanguage === "ja" ? 25 : 5);
|
||||
levelUpStatsContainer.add(levelUpStatsIncrContent);
|
||||
|
||||
this.levelUpStatsIncrContent = levelUpStatsIncrContent;
|
||||
@ -135,7 +133,6 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
|
||||
TextStyle.WINDOW,
|
||||
{ maxLines: 6, lineSpacing: 5 },
|
||||
);
|
||||
levelUpStatsValuesContent.setLineSpacing(i18next.resolvedLanguage === "ja" ? 25 : 5);
|
||||
levelUpStatsValuesContent.setOrigin(1, 0);
|
||||
levelUpStatsValuesContent.setAlign("right");
|
||||
levelUpStatsContainer.add(levelUpStatsValuesContent);
|
||||
|
@ -82,7 +82,7 @@ export default class GameChallengesUiHandler extends UiHandler {
|
||||
headerBg.setName("window-header-bg");
|
||||
headerBg.setOrigin(0, 0);
|
||||
|
||||
const headerText = addTextObject(0, 0, i18next.t("challenges:title"), TextStyle.SETTINGS_LABEL);
|
||||
const headerText = addTextObject(0, 0, i18next.t("challenges:title"), TextStyle.HEADER_LABEL);
|
||||
headerText.setName("text-header");
|
||||
headerText.setOrigin(0, 0);
|
||||
headerText.setPositionRelative(headerBg, 8, 4);
|
||||
|
@ -53,7 +53,12 @@ export default class CommandUiHandler extends UiHandler {
|
||||
this.commandsContainer.add(this.teraButton);
|
||||
|
||||
for (let c = 0; c < commands.length; c++) {
|
||||
const commandText = addTextObject(c % 2 === 0 ? 0 : 55.8, c < 2 ? 0 : 16, commands[c], TextStyle.WINDOW);
|
||||
const commandText = addTextObject(
|
||||
c % 2 === 0 ? 0 : 55.8,
|
||||
c < 2 ? 0 : 16,
|
||||
commands[c],
|
||||
TextStyle.WINDOW_BATTLE_COMMAND,
|
||||
);
|
||||
commandText.setName(commands[c]);
|
||||
this.commandsContainer.add(commandText);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -49,11 +49,11 @@ export default class EggListUiHandler extends MessageUiHandler {
|
||||
|
||||
this.eggNameText = addTextObject(8, 68, "", TextStyle.SUMMARY).setOrigin(0);
|
||||
|
||||
this.eggDateText = addTextObject(8, 91, "", TextStyle.TOOLTIP_CONTENT);
|
||||
this.eggDateText = addTextObject(8, 91, "", TextStyle.EGG_LIST);
|
||||
|
||||
this.eggHatchWavesText = addTextObject(8, 108, "", TextStyle.TOOLTIP_CONTENT).setWordWrapWidth(540);
|
||||
this.eggHatchWavesText = addTextObject(8, 108, "", TextStyle.EGG_LIST).setWordWrapWidth(540);
|
||||
|
||||
this.eggGachaInfoText = addTextObject(8, 152, "", TextStyle.TOOLTIP_CONTENT).setWordWrapWidth(540);
|
||||
this.eggGachaInfoText = addTextObject(8, 152, "", TextStyle.EGG_LIST).setWordWrapWidth(540);
|
||||
|
||||
this.eggListIconContainer = globalScene.add.container(113, 5);
|
||||
|
||||
|
@ -60,7 +60,7 @@ export class FilterBar extends Phaser.GameObjects.Container {
|
||||
|
||||
this.columns.push(column);
|
||||
|
||||
const filterTypesLabel = addTextObject(0, 3, title, TextStyle.TOOLTIP_CONTENT);
|
||||
const filterTypesLabel = addTextObject(0, 3, title, TextStyle.FILTER_BAR_MAIN);
|
||||
this.labels.push(filterTypesLabel);
|
||||
this.add(filterTypesLabel);
|
||||
this.dropDowns.push(dropDown);
|
||||
|
@ -243,7 +243,7 @@ export default class GameStatsUiHandler extends UiHandler {
|
||||
const headerBg = addWindow(0, 0, globalScene.game.canvas.width / 6 - 2, 24);
|
||||
headerBg.setOrigin(0, 0);
|
||||
|
||||
const headerText = addTextObject(0, 0, i18next.t("gameStatsUiHandler:stats"), TextStyle.SETTINGS_LABEL);
|
||||
const headerText = addTextObject(0, 0, i18next.t("gameStatsUiHandler:stats"), TextStyle.HEADER_LABEL);
|
||||
headerText.setOrigin(0, 0);
|
||||
headerText.setPositionRelative(headerBg, 8, 4);
|
||||
|
||||
@ -267,10 +267,10 @@ export default class GameStatsUiHandler extends UiHandler {
|
||||
|
||||
this.statsContainer = globalScene.add.container(0, 0);
|
||||
|
||||
new Array(18).fill(null).map((_, s) => {
|
||||
for (let i = 0; i < 18; i++) {
|
||||
const statLabel = addTextObject(
|
||||
8 + (s % 2 === 1 ? statsBgWidth : 0),
|
||||
28 + Math.floor(s / 2) * 16,
|
||||
8 + (i % 2 === 1 ? statsBgWidth : 0),
|
||||
28 + Math.floor(i / 2) * 16,
|
||||
"",
|
||||
TextStyle.STATS_LABEL,
|
||||
);
|
||||
@ -278,11 +278,11 @@ export default class GameStatsUiHandler extends UiHandler {
|
||||
this.statsContainer.add(statLabel);
|
||||
this.statLabels.push(statLabel);
|
||||
|
||||
const statValue = addTextObject(statsBgWidth * ((s % 2) + 1) - 8, statLabel.y, "", TextStyle.STATS_VALUE);
|
||||
const statValue = addTextObject(statsBgWidth * ((i % 2) + 1) - 8, statLabel.y, "", TextStyle.STATS_VALUE);
|
||||
statValue.setOrigin(1, 0);
|
||||
this.statsContainer.add(statValue);
|
||||
this.statValues.push(statValue);
|
||||
});
|
||||
}
|
||||
|
||||
this.gameStatsContainer.add(headerBg);
|
||||
this.gameStatsContainer.add(headerText);
|
||||
|
@ -80,7 +80,6 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem
|
||||
},
|
||||
},
|
||||
);
|
||||
this.desc.setLineSpacing(i18next.resolvedLanguage === "ja" ? 25 : 5);
|
||||
|
||||
// limit the text rendering, required for scrolling later on
|
||||
const maskPointOrigin = {
|
||||
|
@ -517,7 +517,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
|
||||
descriptionTextObject.setMask(abilityDescriptionTextMask);
|
||||
|
||||
const descriptionLineCount = Math.floor(descriptionTextObject.displayHeight / 10);
|
||||
const descriptionLineCount = Math.floor(descriptionTextObject.displayHeight / 9.2);
|
||||
|
||||
if (this.descriptionScrollTween) {
|
||||
this.descriptionScrollTween.remove();
|
||||
@ -614,6 +614,8 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
const tooltipTextObject = addBBCodeTextObject(6, 7, text, TextStyle.TOOLTIP_CONTENT, {
|
||||
wordWrap: { width: 600 },
|
||||
fontSize: "72px",
|
||||
padding: { top: 8 },
|
||||
lineSpacing: 1.25,
|
||||
});
|
||||
this.tooltipContainer.add(tooltipTextObject);
|
||||
|
||||
@ -627,7 +629,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
const textMask = tooltipTextMaskRect.createGeometryMask();
|
||||
tooltipTextObject.setMask(textMask);
|
||||
|
||||
const tooltipLineCount = Math.floor(tooltipTextObject.displayHeight / 11.2);
|
||||
const tooltipLineCount = Math.floor(tooltipTextObject.displayHeight / 10.2);
|
||||
|
||||
if (this.tooltipScrollTween) {
|
||||
this.tooltipScrollTween.remove();
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { PlayerPokemon, TurnMove } from "#app/field/pokemon";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import type { TurnMove } from "#app/@types/turn-move";
|
||||
import type { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
@ -1937,7 +1938,7 @@ class PartyCancelButton extends Phaser.GameObjects.Container {
|
||||
|
||||
this.partyCancelPb = partyCancelPb;
|
||||
|
||||
const partyCancelText = addTextObject(-8, -7, i18next.t("partyUiHandler:cancel"), TextStyle.PARTY);
|
||||
const partyCancelText = addTextObject(-10, -7, i18next.t("partyUiHandler:cancel"), TextStyle.PARTY_CANCEL_BUTTON);
|
||||
this.add(partyCancelText);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ import type { InfoToggle } from "../battle-scene";
|
||||
import { TextStyle, addTextObject } from "./text";
|
||||
import { addWindow } from "./ui-theme";
|
||||
import { fixedInt } from "#app/utils/common";
|
||||
import i18next from "i18next";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
export interface PokedexInfoOverlaySettings {
|
||||
@ -55,7 +54,6 @@ export default class PokedexInfoOverlay extends Phaser.GameObjects.Container imp
|
||||
this.desc = addTextObject(BORDER, BORDER - 2, "", TextStyle.BATTLE_INFO, {
|
||||
wordWrap: { width: (this.width - (BORDER - 2) * 2) * GLOBAL_SCALE },
|
||||
});
|
||||
this.desc.setLineSpacing(i18next.resolvedLanguage === "ja" ? 25 : 5);
|
||||
|
||||
// limit the text rendering, required for scrolling later on
|
||||
this.maskPointOriginX = options?.x || 0;
|
||||
|
@ -82,13 +82,21 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
||||
instructionTextSize: "38px",
|
||||
},
|
||||
de: {
|
||||
starterInfoTextSize: "48px",
|
||||
starterInfoTextSize: "54px",
|
||||
instructionTextSize: "35px",
|
||||
starterInfoXPos: 33,
|
||||
starterInfoXPos: 35,
|
||||
},
|
||||
"es-ES": {
|
||||
starterInfoTextSize: "56px",
|
||||
instructionTextSize: "35px",
|
||||
starterInfoTextSize: "50px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoYOffset: 0.5,
|
||||
starterInfoXPos: 38,
|
||||
},
|
||||
"es-MX": {
|
||||
starterInfoTextSize: "50px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoYOffset: 0.5,
|
||||
starterInfoXPos: 38,
|
||||
},
|
||||
fr: {
|
||||
starterInfoTextSize: "54px",
|
||||
@ -98,34 +106,53 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
||||
starterInfoTextSize: "56px",
|
||||
instructionTextSize: "38px",
|
||||
},
|
||||
pt_BR: {
|
||||
starterInfoTextSize: "47px",
|
||||
instructionTextSize: "38px",
|
||||
"pt-BR": {
|
||||
starterInfoTextSize: "48px",
|
||||
instructionTextSize: "42px",
|
||||
starterInfoYOffset: 0.5,
|
||||
starterInfoXPos: 33,
|
||||
},
|
||||
zh: {
|
||||
starterInfoTextSize: "47px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoYOffset: 1,
|
||||
starterInfoXPos: 24,
|
||||
},
|
||||
pt: {
|
||||
starterInfoTextSize: "48px",
|
||||
instructionTextSize: "42px",
|
||||
starterInfoXPos: 33,
|
||||
starterInfoTextSize: "56px",
|
||||
instructionTextSize: "36px",
|
||||
starterInfoXPos: 26,
|
||||
},
|
||||
ko: {
|
||||
starterInfoTextSize: "52px",
|
||||
starterInfoTextSize: "60px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoYOffset: -0.5,
|
||||
starterInfoXPos: 30,
|
||||
},
|
||||
ja: {
|
||||
starterInfoTextSize: "51px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoTextSize: "48px",
|
||||
instructionTextSize: "40px",
|
||||
starterInfoYOffset: 1,
|
||||
starterInfoXPos: 32,
|
||||
},
|
||||
"ca-ES": {
|
||||
ca: {
|
||||
starterInfoTextSize: "48px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoYOffset: 0.5,
|
||||
starterInfoXPos: 29,
|
||||
},
|
||||
da: {
|
||||
starterInfoTextSize: "56px",
|
||||
instructionTextSize: "38px",
|
||||
},
|
||||
tr: {
|
||||
starterInfoTextSize: "56px",
|
||||
instructionTextSize: "38px",
|
||||
},
|
||||
ro: {
|
||||
starterInfoTextSize: "56px",
|
||||
instructionTextSize: "38px",
|
||||
},
|
||||
ru: {
|
||||
starterInfoTextSize: "46px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoYOffset: 0.5,
|
||||
starterInfoXPos: 26,
|
||||
},
|
||||
};
|
||||
|
||||
const valueReductionMax = 2;
|
||||
@ -309,7 +336,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.shinyOverlay.setVisible(false);
|
||||
this.starterSelectContainer.add(this.shinyOverlay);
|
||||
|
||||
this.pokemonNumberText = addTextObject(17, 1, "0000", TextStyle.SUMMARY);
|
||||
this.pokemonNumberText = addTextObject(17, 1, "0000", TextStyle.SUMMARY_DEX_NUM);
|
||||
this.pokemonNumberText.setOrigin(0, 0);
|
||||
this.starterSelectContainer.add(this.pokemonNumberText);
|
||||
|
||||
@ -328,7 +355,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.pokemonGrowthRateLabelText.setVisible(false);
|
||||
this.starterSelectContainer.add(this.pokemonGrowthRateLabelText);
|
||||
|
||||
this.pokemonGrowthRateText = addTextObject(34, 106, "", TextStyle.SUMMARY_PINK, { fontSize: "36px" });
|
||||
this.pokemonGrowthRateText = addTextObject(34, 106, "", TextStyle.GROWTH_RATE_TYPE, { fontSize: "36px" });
|
||||
this.pokemonGrowthRateText.setOrigin(0, 0);
|
||||
this.starterSelectContainer.add(this.pokemonGrowthRateText);
|
||||
|
||||
@ -371,9 +398,15 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.pokemonLuckLabelText.setOrigin(0, 0);
|
||||
this.starterSelectContainer.add(this.pokemonLuckLabelText);
|
||||
|
||||
this.pokemonLuckText = addTextObject(8 + this.pokemonLuckLabelText.displayWidth + 2, 89, "0", TextStyle.WINDOW, {
|
||||
fontSize: "56px",
|
||||
});
|
||||
this.pokemonLuckText = addTextObject(
|
||||
8 + this.pokemonLuckLabelText.displayWidth + 2,
|
||||
89,
|
||||
"0",
|
||||
TextStyle.LUCK_VALUE,
|
||||
{
|
||||
fontSize: "56px",
|
||||
},
|
||||
);
|
||||
this.pokemonLuckText.setOrigin(0, 0);
|
||||
this.starterSelectContainer.add(this.pokemonLuckText);
|
||||
|
||||
@ -470,7 +503,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.instructionRowX + this.instructionRowTextOffset,
|
||||
this.instructionRowY,
|
||||
i18next.t("pokedexUiHandler:candyUpgrade"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.candyUpgradeLabel.setName("text-candyUpgrade-label");
|
||||
@ -491,7 +524,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.instructionRowX + this.instructionRowTextOffset,
|
||||
this.instructionRowY,
|
||||
i18next.t("pokedexUiHandler:cycleShiny"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.shinyLabel.setName("text-shiny-label");
|
||||
@ -510,7 +543,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.instructionRowX + this.instructionRowTextOffset,
|
||||
this.instructionRowY,
|
||||
i18next.t("pokedexUiHandler:cycleForm"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.formLabel.setName("text-form-label");
|
||||
@ -529,7 +562,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.instructionRowX + this.instructionRowTextOffset,
|
||||
this.instructionRowY,
|
||||
i18next.t("pokedexUiHandler:cycleGender"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.genderLabel.setName("text-gender-label");
|
||||
@ -548,7 +581,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.instructionRowX + this.instructionRowTextOffset,
|
||||
this.instructionRowY,
|
||||
i18next.t("pokedexUiHandler:cycleVariant"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.variantLabel.setName("text-variant-label");
|
||||
@ -557,9 +590,15 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.showBackSpriteIconElement.setName("show-backSprite-icon-element");
|
||||
this.showBackSpriteIconElement.setScale(0.675);
|
||||
this.showBackSpriteIconElement.setOrigin(0.0, 0.0);
|
||||
this.showBackSpriteLabel = addTextObject(60, 7, i18next.t("pokedexUiHandler:showBackSprite"), TextStyle.PARTY, {
|
||||
fontSize: instructionTextSize,
|
||||
});
|
||||
this.showBackSpriteLabel = addTextObject(
|
||||
60,
|
||||
7,
|
||||
i18next.t("pokedexUiHandler:showBackSprite"),
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{
|
||||
fontSize: instructionTextSize,
|
||||
},
|
||||
);
|
||||
this.showBackSpriteLabel.setName("show-backSprite-label");
|
||||
this.starterSelectContainer.add(this.showBackSpriteIconElement);
|
||||
this.starterSelectContainer.add(this.showBackSpriteLabel);
|
||||
@ -1899,14 +1938,14 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
if (!(passiveAttr & PassiveAttr.UNLOCKED)) {
|
||||
const passiveCost = getPassiveCandyCount(speciesStarterCosts[this.starterId]);
|
||||
options.push({
|
||||
label: `x${passiveCost} ${i18next.t("pokedexUiHandler:unlockPassive")}`,
|
||||
label: `×${passiveCost} ${i18next.t("pokedexUiHandler:unlockPassive")}`,
|
||||
handler: () => {
|
||||
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= passiveCost) {
|
||||
starterData.passiveAttr |= PassiveAttr.UNLOCKED | PassiveAttr.ENABLED;
|
||||
if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
|
||||
starterData.candyCount -= passiveCost;
|
||||
}
|
||||
this.pokemonCandyCountText.setText(`x${starterData.candyCount}`);
|
||||
this.pokemonCandyCountText.setText(`×${starterData.candyCount}`);
|
||||
globalScene.gameData.saveSystem().then(success => {
|
||||
if (!success) {
|
||||
return globalScene.reset(true);
|
||||
@ -1931,14 +1970,14 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
if (valueReduction < valueReductionMax) {
|
||||
const reductionCost = getValueReductionCandyCounts(speciesStarterCosts[this.starterId])[valueReduction];
|
||||
options.push({
|
||||
label: `x${reductionCost} ${i18next.t("pokedexUiHandler:reduceCost")}`,
|
||||
label: `×${reductionCost} ${i18next.t("pokedexUiHandler:reduceCost")}`,
|
||||
handler: () => {
|
||||
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= reductionCost) {
|
||||
starterData.valueReduction++;
|
||||
if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
|
||||
starterData.candyCount -= reductionCost;
|
||||
}
|
||||
this.pokemonCandyCountText.setText(`x${starterData.candyCount}`);
|
||||
this.pokemonCandyCountText.setText(`×${starterData.candyCount}`);
|
||||
globalScene.gameData.saveSystem().then(success => {
|
||||
if (!success) {
|
||||
return globalScene.reset(true);
|
||||
@ -1960,7 +1999,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
// Same species egg menu option.
|
||||
const sameSpeciesEggCost = getSameSpeciesEggCandyCounts(speciesStarterCosts[this.starterId]);
|
||||
options.push({
|
||||
label: `x${sameSpeciesEggCost} ${i18next.t("pokedexUiHandler:sameSpeciesEgg")}`,
|
||||
label: `×${sameSpeciesEggCost} ${i18next.t("pokedexUiHandler:sameSpeciesEgg")}`,
|
||||
handler: () => {
|
||||
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost) {
|
||||
if (globalScene.gameData.eggs.length >= 99 && !Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) {
|
||||
@ -1979,7 +2018,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
|
||||
starterData.candyCount -= sameSpeciesEggCost;
|
||||
}
|
||||
this.pokemonCandyCountText.setText(`x${starterData.candyCount}`);
|
||||
this.pokemonCandyCountText.setText(`×${starterData.candyCount}`);
|
||||
|
||||
const egg = new Egg({
|
||||
scene: globalScene,
|
||||
@ -2480,9 +2519,11 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
const isFormSeen = this.isSeen();
|
||||
|
||||
this.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default?
|
||||
this.pokemonNumberText.setColor(this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, false));
|
||||
this.pokemonNumberText.setColor(
|
||||
this.getTextColor(shiny ? TextStyle.SUMMARY_DEX_NUM_GOLD : TextStyle.SUMMARY_DEX_NUM, false),
|
||||
);
|
||||
this.pokemonNumberText.setShadowColor(
|
||||
this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, true),
|
||||
this.getTextColor(shiny ? TextStyle.SUMMARY_DEX_NUM_GOLD : TextStyle.SUMMARY_DEX_NUM, true),
|
||||
);
|
||||
|
||||
const assetLoadCancelled = new BooleanHolder(false);
|
||||
@ -2634,7 +2675,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.pokemonCandyIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[0])));
|
||||
this.pokemonCandyOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1])));
|
||||
this.pokemonCandyCountText.setText(
|
||||
`x${species.speciesId === SpeciesId.PIKACHU ? 0 : globalScene.gameData.starterData[this.starterId].candyCount}`,
|
||||
`×${species.speciesId === SpeciesId.PIKACHU ? 0 : globalScene.gameData.starterData[this.starterId].candyCount}`,
|
||||
);
|
||||
this.pokemonCandyContainer.setVisible(true);
|
||||
|
||||
|
@ -471,7 +471,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
this.pokemonNameText.setOrigin(0, 0);
|
||||
this.starterSelectContainer.add(this.pokemonNameText);
|
||||
|
||||
this.pokemonFormText = addTextObject(6, 121, "", TextStyle.PARTY, {
|
||||
this.pokemonFormText = addTextObject(6, 121, "", TextStyle.INSTRUCTIONS_TEXT, {
|
||||
fontSize: textSettings.instructionTextSize,
|
||||
});
|
||||
this.pokemonFormText.setOrigin(0, 0);
|
||||
@ -483,13 +483,14 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
|
||||
starterBoxContainer.add(this.starterSelectScrollBar);
|
||||
|
||||
this.pokerusCursorObjs = new Array(POKERUS_STARTER_COUNT).fill(null).map(() => {
|
||||
this.pokerusCursorObjs = [];
|
||||
for (let i = 0; i < POKERUS_STARTER_COUNT; i++) {
|
||||
const cursorObj = globalScene.add.image(0, 0, "select_cursor_pokerus");
|
||||
cursorObj.setVisible(false);
|
||||
cursorObj.setOrigin(0, 0);
|
||||
starterBoxContainer.add(cursorObj);
|
||||
return cursorObj;
|
||||
});
|
||||
this.pokerusCursorObjs.push(cursorObj);
|
||||
}
|
||||
|
||||
this.cursorObj = globalScene.add.image(0, 0, "select_cursor");
|
||||
this.cursorObj.setOrigin(0, 0);
|
||||
@ -561,7 +562,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
this.goFilterIconElement2.setName("sprite-goFilter2-icon-element");
|
||||
this.goFilterIconElement2.setScale(0.675);
|
||||
this.goFilterIconElement2.setOrigin(0.0, 0.0);
|
||||
this.goFilterLabel = addTextObject(30, 2, i18next.t("pokedexUiHandler:goFilters"), TextStyle.PARTY, {
|
||||
this.goFilterLabel = addTextObject(30, 2, i18next.t("pokedexUiHandler:goFilters"), TextStyle.INSTRUCTIONS_TEXT, {
|
||||
fontSize: instructionTextSize,
|
||||
});
|
||||
this.goFilterLabel.setName("text-goFilter-label");
|
||||
@ -577,7 +578,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
20,
|
||||
10,
|
||||
i18next.t("pokedexUiHandler:toggleDecorations"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.toggleDecorationsLabel.setName("text-toggleDecorations-label");
|
||||
@ -588,9 +589,15 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
this.showFormTrayIconElement.setName("sprite-showFormTray-icon-element");
|
||||
this.showFormTrayIconElement.setScale(0.675);
|
||||
this.showFormTrayIconElement.setOrigin(0.0, 0.0);
|
||||
this.showFormTrayLabel = addTextObject(16, 168, i18next.t("pokedexUiHandler:showForms"), TextStyle.PARTY, {
|
||||
fontSize: instructionTextSize,
|
||||
});
|
||||
this.showFormTrayLabel = addTextObject(
|
||||
16,
|
||||
168,
|
||||
i18next.t("pokedexUiHandler:showForms"),
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{
|
||||
fontSize: instructionTextSize,
|
||||
},
|
||||
);
|
||||
this.showFormTrayLabel.setName("text-showFormTray-label");
|
||||
this.showFormTrayIconElement.setVisible(false);
|
||||
this.showFormTrayLabel.setVisible(false);
|
||||
|
@ -49,13 +49,11 @@ export default class PokemonHatchInfoContainer extends PokemonInfoContainer {
|
||||
this.pokemonListContainer.add(this.currentPokemonSprite);
|
||||
|
||||
// setup name and number
|
||||
this.pokemonNumberText = addTextObject(80, 107.5, "0000", TextStyle.SUMMARY, { fontSize: 74 });
|
||||
this.pokemonNumberText = addTextObject(84, 107, "0000", TextStyle.EGG_SUMMARY_DEX, { fontSize: 78 });
|
||||
this.pokemonNumberText.setOrigin(0, 0);
|
||||
this.pokemonListContainer.add(this.pokemonNumberText);
|
||||
|
||||
this.pokemonNameText = addTextObject(7, 107.5, "", TextStyle.SUMMARY, {
|
||||
fontSize: 74,
|
||||
});
|
||||
this.pokemonNameText = addTextObject(7, 109, "", TextStyle.EGG_SUMMARY_NAME, { fontSize: 64 });
|
||||
this.pokemonNameText.setOrigin(0, 0);
|
||||
this.pokemonListContainer.add(this.pokemonNameText);
|
||||
|
||||
@ -93,7 +91,7 @@ export default class PokemonHatchInfoContainer extends PokemonInfoContainer {
|
||||
const eggMoveBg = globalScene.add.nineslice(70, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2);
|
||||
eggMoveBg.setOrigin(1, 0);
|
||||
|
||||
const eggMoveLabel = addTextObject(70 - eggMoveBg.width / 2, 0, "???", TextStyle.PARTY);
|
||||
const eggMoveLabel = addTextObject(70 - eggMoveBg.width / 2, 0, "???", TextStyle.MOVE_LABEL);
|
||||
eggMoveLabel.setOrigin(0.5, 0);
|
||||
|
||||
this.pokemonEggMoveBgs.push(eggMoveBg);
|
||||
@ -158,7 +156,7 @@ export default class PokemonHatchInfoContainer extends PokemonInfoContainer {
|
||||
this.pokemonCandyIcon.setVisible(true);
|
||||
this.pokemonCandyOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1])));
|
||||
this.pokemonCandyOverlayIcon.setVisible(true);
|
||||
this.pokemonCandyCountText.setText(`x${globalScene.gameData.starterData[species.speciesId].candyCount}`);
|
||||
this.pokemonCandyCountText.setText(`×${globalScene.gameData.starterData[species.speciesId].candyCount}`);
|
||||
this.pokemonCandyCountText.setVisible(true);
|
||||
|
||||
this.pokemonNumberText.setText(padInt(species.speciesId, 4));
|
||||
|
@ -22,11 +22,21 @@ interface LanguageSetting {
|
||||
}
|
||||
|
||||
const languageSettings: { [key: string]: LanguageSetting } = {
|
||||
en: {
|
||||
infoContainerTextSize: "64px",
|
||||
infoContainerLabelXPos: -20,
|
||||
infoContainerTextXPos: -17,
|
||||
},
|
||||
pt: {
|
||||
infoContainerTextSize: "60px",
|
||||
infoContainerLabelXPos: -15,
|
||||
infoContainerTextXPos: -12,
|
||||
},
|
||||
ja: {
|
||||
infoContainerTextSize: "64px",
|
||||
infoContainerLabelXPos: -27,
|
||||
infoContainerTextXPos: -25,
|
||||
},
|
||||
};
|
||||
|
||||
export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
|
||||
@ -106,7 +116,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
|
||||
moveBg.setOrigin(1, 0);
|
||||
moveBg.setName("nineslice-move-bg");
|
||||
|
||||
const moveLabel = addTextObject(-moveBg.width / 2, 0, "-", TextStyle.PARTY);
|
||||
const moveLabel = addTextObject(-moveBg.width / 2, 0, "-", TextStyle.MOVE_LABEL);
|
||||
moveLabel.setOrigin(0.5, 0);
|
||||
moveLabel.setName("text-move-label");
|
||||
|
||||
@ -445,12 +455,12 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
|
||||
this.pokemonShinyIcon.setPosition(82, 87);
|
||||
this.pokemonShinyNewIcon.setPosition(72, 87);
|
||||
|
||||
this.pokemonFormLabelText.setPosition(infoContainerLabelXPos, 152);
|
||||
this.pokemonFormText.setPosition(infoContainerTextXPos, 152);
|
||||
this.pokemonAbilityLabelText.setPosition(infoContainerLabelXPos, 110);
|
||||
this.pokemonAbilityText.setPosition(infoContainerTextXPos, 110);
|
||||
this.pokemonNatureLabelText.setPosition(infoContainerLabelXPos, 125);
|
||||
this.pokemonNatureText.setPosition(infoContainerTextXPos, 125);
|
||||
this.pokemonFormLabelText.setPosition(infoContainerLabelXPos, 153);
|
||||
this.pokemonFormText.setPosition(infoContainerTextXPos, 153);
|
||||
this.pokemonAbilityLabelText.setPosition(infoContainerLabelXPos, 111);
|
||||
this.pokemonAbilityText.setPosition(infoContainerTextXPos, 111);
|
||||
this.pokemonNatureLabelText.setPosition(infoContainerLabelXPos, 126);
|
||||
this.pokemonNatureText.setPosition(infoContainerTextXPos, 126);
|
||||
|
||||
this.statsContainer.setScale(0.7);
|
||||
this.statsContainer.setPosition(30, -3);
|
||||
|
@ -202,7 +202,7 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
);
|
||||
this.runContainer.add(abilityButtonContainer);
|
||||
}
|
||||
const headerText = addTextObject(0, 0, i18next.t("runHistory:runInfo"), TextStyle.SETTINGS_LABEL);
|
||||
const headerText = addTextObject(0, 0, i18next.t("runHistory:runInfo"), TextStyle.HEADER_LABEL);
|
||||
headerText.setOrigin(0, 0);
|
||||
headerText.setPositionRelative(headerBg, 8, 4);
|
||||
this.runContainer.add(headerText);
|
||||
@ -603,7 +603,7 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
// Duration + Money
|
||||
const runInfoTextContainer = globalScene.add.container(0, 0);
|
||||
// Japanese is set to a greater line spacing of 35px in addBBCodeTextObject() if lineSpacing < 12.
|
||||
const lineSpacing = i18next.resolvedLanguage === "ja" ? 12 : 3;
|
||||
const lineSpacing = i18next.resolvedLanguage === "ja" ? 3 : 3;
|
||||
const runInfoText = addBBCodeTextObject(7, 0, "", TextStyle.WINDOW, {
|
||||
fontSize: "50px",
|
||||
lineSpacing: lineSpacing,
|
||||
@ -768,7 +768,7 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
const pPassiveInfo = pokemon.passive ? passiveLabel + ": " + pokemon.getPassiveAbility().name : "";
|
||||
const pAbilityInfo = abilityLabel + ": " + pokemon.getAbility().name;
|
||||
// Japanese is set to a greater line spacing of 35px in addBBCodeTextObject() if lineSpacing < 12.
|
||||
const lineSpacing = i18next.resolvedLanguage === "ja" ? 12 : 3;
|
||||
const lineSpacing = i18next.resolvedLanguage === "ja" ? 3 : 3;
|
||||
const pokeInfoText = addBBCodeTextObject(0, 0, pName, TextStyle.SUMMARY, {
|
||||
fontSize: textContainerFontSize,
|
||||
lineSpacing: lineSpacing,
|
||||
@ -866,7 +866,7 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
moveContainer.setScale(0.5);
|
||||
const moveBg = globalScene.add.nineslice(0, 0, "type_bgs", "unknown", 85, 15, 2, 2, 2, 2);
|
||||
moveBg.setOrigin(1, 0);
|
||||
const moveLabel = addTextObject(-moveBg.width / 2, 2, "-", TextStyle.PARTY);
|
||||
const moveLabel = addTextObject(-moveBg.width / 2, 1, "-", TextStyle.MOVE_LABEL);
|
||||
moveLabel.setOrigin(0.5, 0);
|
||||
moveLabel.setName("text-move-label");
|
||||
pokemonMoveBgs.push(moveBg);
|
||||
|
@ -144,7 +144,7 @@ export default class NavigationMenu extends Phaser.GameObjects.Container {
|
||||
let relative: Phaser.GameObjects.Sprite | Phaser.GameObjects.Text = iconPreviousTab;
|
||||
let relativeWidth: number = iconPreviousTab.width * 6;
|
||||
for (const label of navigationManager.labels) {
|
||||
const labelText = addTextObject(0, 0, label, TextStyle.SETTINGS_LABEL);
|
||||
const labelText = addTextObject(0, 0, label, TextStyle.SETTINGS_LABEL_NAVBAR);
|
||||
labelText.setOrigin(0, 0);
|
||||
labelText.setPositionRelative(relative, 6 + relativeWidth / 6, 0);
|
||||
this.add(labelText);
|
||||
|
@ -148,10 +148,10 @@ const languageSettings: { [key: string]: LanguageSetting } = {
|
||||
starterInfoXPos: 30,
|
||||
},
|
||||
ja: {
|
||||
starterInfoTextSize: "62px",
|
||||
instructionTextSize: "38px",
|
||||
starterInfoYOffset: 0.5,
|
||||
starterInfoXPos: 33,
|
||||
starterInfoTextSize: "48px",
|
||||
instructionTextSize: "40px",
|
||||
starterInfoYOffset: 1,
|
||||
starterInfoXPos: 32,
|
||||
},
|
||||
ca: {
|
||||
starterInfoTextSize: "48px",
|
||||
@ -624,7 +624,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
});
|
||||
this.starterSelectContainer.add(this.pokemonSprite);
|
||||
|
||||
this.pokemonNumberText = addTextObject(17, 1, "0000", TextStyle.SUMMARY);
|
||||
this.pokemonNumberText = addTextObject(17, 1, "0000", TextStyle.SUMMARY_DEX_NUM);
|
||||
this.pokemonNumberText.setOrigin(0, 0);
|
||||
this.starterSelectContainer.add(this.pokemonNumberText);
|
||||
|
||||
@ -643,7 +643,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.pokemonGrowthRateLabelText.setVisible(false);
|
||||
this.starterSelectContainer.add(this.pokemonGrowthRateLabelText);
|
||||
|
||||
this.pokemonGrowthRateText = addTextObject(34, 106, "", TextStyle.SUMMARY_PINK, { fontSize: "36px" });
|
||||
this.pokemonGrowthRateText = addTextObject(34, 106, "", TextStyle.GROWTH_RATE_TYPE, { fontSize: "36px" });
|
||||
this.pokemonGrowthRateText.setOrigin(0, 0);
|
||||
this.starterSelectContainer.add(this.pokemonGrowthRateText);
|
||||
|
||||
@ -743,7 +743,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.pokemonEggMoveBgs = [];
|
||||
this.pokemonEggMoveLabels = [];
|
||||
|
||||
this.valueLimitLabel = addTextObject(teamWindowX + 17, 150, "0/10", TextStyle.TOOLTIP_CONTENT);
|
||||
this.valueLimitLabel = addTextObject(teamWindowX + 17, 150, "0/10", TextStyle.STARTER_VALUE_LIMIT);
|
||||
this.valueLimitLabel.setOrigin(0.5, 0);
|
||||
this.starterSelectContainer.add(this.valueLimitLabel);
|
||||
|
||||
@ -800,21 +800,23 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
|
||||
starterBoxContainer.add(this.starterSelectScrollBar);
|
||||
|
||||
this.pokerusCursorObjs = new Array(POKERUS_STARTER_COUNT).fill(null).map(() => {
|
||||
this.pokerusCursorObjs = [];
|
||||
for (let i = 0; i < POKERUS_STARTER_COUNT; i++) {
|
||||
const cursorObj = globalScene.add.image(0, 0, "select_cursor_pokerus");
|
||||
cursorObj.setVisible(false);
|
||||
cursorObj.setOrigin(0, 0);
|
||||
starterBoxContainer.add(cursorObj);
|
||||
return cursorObj;
|
||||
});
|
||||
this.pokerusCursorObjs.push(cursorObj);
|
||||
}
|
||||
|
||||
this.starterCursorObjs = new Array(6).fill(null).map(() => {
|
||||
this.starterCursorObjs = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const cursorObj = globalScene.add.image(0, 0, "select_cursor_highlight");
|
||||
cursorObj.setVisible(false);
|
||||
cursorObj.setOrigin(0, 0);
|
||||
starterBoxContainer.add(cursorObj);
|
||||
return cursorObj;
|
||||
});
|
||||
this.starterCursorObjs.push(cursorObj);
|
||||
}
|
||||
|
||||
this.cursorObj = globalScene.add.image(0, 0, "select_cursor");
|
||||
this.cursorObj.setOrigin(0, 0);
|
||||
@ -843,15 +845,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
|
||||
this.starterSelectContainer.add(starterBoxContainer);
|
||||
|
||||
this.starterIcons = new Array(6).fill(null).map((_, i) => {
|
||||
this.starterIcons = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const icon = globalScene.add.sprite(teamWindowX + 7, calcStarterIconY(i), "pokemon_icons_0");
|
||||
icon.setScale(0.5);
|
||||
icon.setOrigin(0, 0);
|
||||
icon.setFrame("unknown");
|
||||
this.starterSelectContainer.add(icon);
|
||||
this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.PASSIVE);
|
||||
return icon;
|
||||
});
|
||||
this.starterIcons.push(icon);
|
||||
}
|
||||
|
||||
this.type1Icon = globalScene.add.sprite(8, 98, getLocalizedSpriteKey("types"));
|
||||
this.type1Icon.setScale(0.5);
|
||||
@ -869,9 +872,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.pokemonLuckLabelText.setOrigin(0, 0);
|
||||
this.starterSelectContainer.add(this.pokemonLuckLabelText);
|
||||
|
||||
this.pokemonLuckText = addTextObject(8 + this.pokemonLuckLabelText.displayWidth + 2, 89, "0", TextStyle.WINDOW, {
|
||||
fontSize: "56px",
|
||||
});
|
||||
this.pokemonLuckText = addTextObject(
|
||||
8 + this.pokemonLuckLabelText.displayWidth + 2,
|
||||
89,
|
||||
"0",
|
||||
TextStyle.LUCK_VALUE,
|
||||
{
|
||||
fontSize: "56px",
|
||||
},
|
||||
);
|
||||
this.pokemonLuckText.setOrigin(0, 0);
|
||||
this.starterSelectContainer.add(this.pokemonLuckText);
|
||||
|
||||
@ -944,7 +953,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
const moveBg = globalScene.add.nineslice(0, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2);
|
||||
moveBg.setOrigin(1, 0);
|
||||
|
||||
const moveLabel = addTextObject(-moveBg.width / 2, 0, "-", TextStyle.PARTY);
|
||||
const moveLabel = addTextObject(-moveBg.width / 2, 0, "-", TextStyle.MOVE_LABEL);
|
||||
moveLabel.setOrigin(0.5, 0);
|
||||
|
||||
this.pokemonMoveBgs.push(moveBg);
|
||||
@ -961,7 +970,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
-this.pokemonMoveBgs[0].width / 2,
|
||||
56,
|
||||
"(+0)",
|
||||
TextStyle.PARTY,
|
||||
TextStyle.MOVE_LABEL,
|
||||
);
|
||||
this.pokemonAdditionalMoveCountLabel.setOrigin(0.5, 0);
|
||||
|
||||
@ -983,7 +992,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
const eggMoveBg = globalScene.add.nineslice(0, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2);
|
||||
eggMoveBg.setOrigin(1, 0);
|
||||
|
||||
const eggMoveLabel = addTextObject(-eggMoveBg.width / 2, 0, "???", TextStyle.PARTY);
|
||||
const eggMoveLabel = addTextObject(-eggMoveBg.width / 2, 0, "???", TextStyle.MOVE_LABEL);
|
||||
eggMoveLabel.setOrigin(0.5, 0);
|
||||
|
||||
this.pokemonEggMoveBgs.push(eggMoveBg);
|
||||
@ -1027,7 +1036,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.instructionRowX + this.instructionRowTextOffset,
|
||||
this.instructionRowY,
|
||||
i18next.t("starterSelectUiHandler:cycleShiny"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.shinyLabel.setName("text-shiny-label");
|
||||
@ -1046,7 +1055,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.instructionRowX + this.instructionRowTextOffset,
|
||||
this.instructionRowY,
|
||||
i18next.t("starterSelectUiHandler:cycleForm"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.formLabel.setName("text-form-label");
|
||||
@ -1065,7 +1074,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.instructionRowX + this.instructionRowTextOffset,
|
||||
this.instructionRowY,
|
||||
i18next.t("starterSelectUiHandler:cycleGender"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.genderLabel.setName("text-gender-label");
|
||||
@ -1084,7 +1093,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.instructionRowX + this.instructionRowTextOffset,
|
||||
this.instructionRowY,
|
||||
i18next.t("starterSelectUiHandler:cycleAbility"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.abilityLabel.setName("text-ability-label");
|
||||
@ -1103,7 +1112,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.instructionRowX + this.instructionRowTextOffset,
|
||||
this.instructionRowY,
|
||||
i18next.t("starterSelectUiHandler:cycleNature"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.natureLabel.setName("text-nature-label");
|
||||
@ -1122,7 +1131,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.instructionRowX + this.instructionRowTextOffset,
|
||||
this.instructionRowY,
|
||||
i18next.t("starterSelectUiHandler:cycleTera"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.teraLabel.setName("text-tera-label");
|
||||
@ -1141,7 +1150,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.filterInstructionRowX + this.instructionRowTextOffset,
|
||||
this.filterInstructionRowY,
|
||||
i18next.t("starterSelectUiHandler:goFilter"),
|
||||
TextStyle.PARTY,
|
||||
TextStyle.INSTRUCTIONS_TEXT,
|
||||
{ fontSize: instructionTextSize },
|
||||
);
|
||||
this.goFilterLabel.setName("text-goFilter-label");
|
||||
@ -2202,14 +2211,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
if (!(passiveAttr & PassiveAttr.UNLOCKED)) {
|
||||
const passiveCost = getPassiveCandyCount(speciesStarterCosts[this.lastSpecies.speciesId]);
|
||||
options.push({
|
||||
label: `x${passiveCost} ${i18next.t("starterSelectUiHandler:unlockPassive")}`,
|
||||
label: `×${passiveCost} ${i18next.t("starterSelectUiHandler:unlockPassive")}`,
|
||||
handler: () => {
|
||||
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= passiveCost) {
|
||||
starterData.passiveAttr |= PassiveAttr.UNLOCKED | PassiveAttr.ENABLED;
|
||||
if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
|
||||
starterData.candyCount -= passiveCost;
|
||||
}
|
||||
this.pokemonCandyCountText.setText(`x${starterData.candyCount}`);
|
||||
this.pokemonCandyCountText.setText(`×${starterData.candyCount}`);
|
||||
globalScene.gameData.saveSystem().then(success => {
|
||||
if (!success) {
|
||||
return globalScene.reset(true);
|
||||
@ -2242,14 +2251,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
valueReduction
|
||||
];
|
||||
options.push({
|
||||
label: `x${reductionCost} ${i18next.t("starterSelectUiHandler:reduceCost")}`,
|
||||
label: `×${reductionCost} ${i18next.t("starterSelectUiHandler:reduceCost")}`,
|
||||
handler: () => {
|
||||
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= reductionCost) {
|
||||
starterData.valueReduction++;
|
||||
if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
|
||||
starterData.candyCount -= reductionCost;
|
||||
}
|
||||
this.pokemonCandyCountText.setText(`x${starterData.candyCount}`);
|
||||
this.pokemonCandyCountText.setText(`×${starterData.candyCount}`);
|
||||
globalScene.gameData.saveSystem().then(success => {
|
||||
if (!success) {
|
||||
return globalScene.reset(true);
|
||||
@ -2276,7 +2285,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
// Same species egg menu option.
|
||||
const sameSpeciesEggCost = getSameSpeciesEggCandyCounts(speciesStarterCosts[this.lastSpecies.speciesId]);
|
||||
options.push({
|
||||
label: `x${sameSpeciesEggCost} ${i18next.t("starterSelectUiHandler:sameSpeciesEgg")}`,
|
||||
label: `×${sameSpeciesEggCost} ${i18next.t("starterSelectUiHandler:sameSpeciesEgg")}`,
|
||||
handler: () => {
|
||||
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost) {
|
||||
if (globalScene.gameData.eggs.length >= 99 && !Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) {
|
||||
@ -2295,7 +2304,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
|
||||
starterData.candyCount -= sameSpeciesEggCost;
|
||||
}
|
||||
this.pokemonCandyCountText.setText(`x${starterData.candyCount}`);
|
||||
this.pokemonCandyCountText.setText(`×${starterData.candyCount}`);
|
||||
|
||||
const egg = new Egg({
|
||||
species: this.lastSpecies.speciesId,
|
||||
@ -3564,7 +3573,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.pokemonShinyIcon.setY(117);
|
||||
this.pokemonCandyIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[0])));
|
||||
this.pokemonCandyOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1])));
|
||||
this.pokemonCandyCountText.setText(`x${globalScene.gameData.starterData[species.speciesId].candyCount}`);
|
||||
this.pokemonCandyCountText.setText(`×${globalScene.gameData.starterData[species.speciesId].candyCount}`);
|
||||
this.pokemonCandyContainer.setVisible(true);
|
||||
this.pokemonFormText.setY(42);
|
||||
this.pokemonHatchedIcon.setVisible(true);
|
||||
@ -3818,9 +3827,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
}
|
||||
|
||||
this.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default?
|
||||
this.pokemonNumberText.setColor(this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, false));
|
||||
this.pokemonNumberText.setColor(
|
||||
this.getTextColor(shiny ? TextStyle.SUMMARY_DEX_NUM_GOLD : TextStyle.SUMMARY_DEX_NUM, false),
|
||||
);
|
||||
this.pokemonNumberText.setShadowColor(
|
||||
this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, true),
|
||||
this.getTextColor(shiny ? TextStyle.SUMMARY_DEX_NUM_GOLD : TextStyle.SUMMARY_DEX_NUM, true),
|
||||
);
|
||||
|
||||
if (forSeen ? this.speciesStarterDexEntry?.seenAttr : this.speciesStarterDexEntry?.caughtAttr) {
|
||||
|
@ -19,7 +19,7 @@ const ivLabelOffset = [0, sideLabelOffset, -sideLabelOffset, sideLabelOffset, -s
|
||||
const ivChartLabelyOffset = [0, 5, 0, 5, 0, 0]; // doing this so attack does not overlap with (+N)
|
||||
const ivChartStatIndexes = [0, 1, 2, 5, 4, 3]; // swap special attack and speed
|
||||
|
||||
const defaultIvChartData = new Array(12).fill(null).map(() => 0);
|
||||
const defaultIvChartData: number[] = new Array(12).fill(0);
|
||||
|
||||
export class StatsContainer extends Phaser.GameObjects.Container {
|
||||
private showDiff: boolean;
|
||||
@ -86,7 +86,7 @@ export class StatsContainer extends Phaser.GameObjects.Container {
|
||||
4 +
|
||||
(this.showDiff ? 0 : ivChartLabelyOffset[s]),
|
||||
i18next.t(getStatKey(s)),
|
||||
TextStyle.TOOLTIP_CONTENT,
|
||||
TextStyle.STATS_HEXAGON,
|
||||
);
|
||||
statLabel.setOrigin(0.5);
|
||||
|
||||
@ -94,7 +94,7 @@ export class StatsContainer extends Phaser.GameObjects.Container {
|
||||
statLabel.x - (this.showDiff ? 0 : ivLabelOffset[s]),
|
||||
statLabel.y + 8,
|
||||
"0",
|
||||
TextStyle.TOOLTIP_CONTENT,
|
||||
TextStyle.STATS_HEXAGON,
|
||||
);
|
||||
this.ivStatValueTexts[s].setOrigin(0.5);
|
||||
|
||||
|
@ -148,7 +148,7 @@ export default class SummaryUiHandler extends UiHandler {
|
||||
this.tabSprite.setOrigin(1, 1);
|
||||
this.summaryContainer.add(this.tabSprite);
|
||||
|
||||
const summaryLabel = addTextObject(4, -165, i18next.t("pokemonSummary:pokemonInfo"), TextStyle.SUMMARY);
|
||||
const summaryLabel = addTextObject(4, -165, i18next.t("pokemonSummary:pokemonInfo"), TextStyle.SUMMARY_HEADER);
|
||||
summaryLabel.setOrigin(0, 1);
|
||||
this.summaryContainer.add(summaryLabel);
|
||||
|
||||
@ -418,7 +418,7 @@ export default class SummaryUiHandler extends UiHandler {
|
||||
}
|
||||
|
||||
this.candyCountText.setText(
|
||||
`x${globalScene.gameData.starterData[this.pokemon.species.getRootSpeciesId()].candyCount}`,
|
||||
`×${globalScene.gameData.starterData[this.pokemon.species.getRootSpeciesId()].candyCount}`,
|
||||
);
|
||||
|
||||
this.candyShadow.setCrop(0, 0, 16, candyCropY);
|
||||
@ -430,7 +430,7 @@ export default class SummaryUiHandler extends UiHandler {
|
||||
this.friendshipShadow.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||
}
|
||||
|
||||
this.friendshipText.setText(`${this.pokemon?.friendship || "0"} / 255`);
|
||||
this.friendshipText.setText(` ${this.pokemon?.friendship || "0"}/255`);
|
||||
|
||||
this.friendshipShadow.setCrop(0, 0, 16, 16 - 16 * ((this.pokemon?.friendship || 0) / 255));
|
||||
|
||||
@ -864,7 +864,7 @@ export default class SummaryUiHandler extends UiHandler {
|
||||
141 + luckLabelText.displayWidth + 2,
|
||||
28,
|
||||
this.pokemon.getLuck().toString(),
|
||||
TextStyle.SUMMARY,
|
||||
TextStyle.LUCK_VALUE,
|
||||
);
|
||||
luckText.setOrigin(0, 0);
|
||||
luckText.setTint(getVariantTint(Math.min(this.pokemon.getLuck() - 1, 2) as Variant));
|
||||
@ -917,11 +917,11 @@ export default class SummaryUiHandler extends UiHandler {
|
||||
abilityInfo.labelImage.setOrigin(0, 0);
|
||||
profileContainer.add(abilityInfo.labelImage);
|
||||
|
||||
abilityInfo.nameText = addTextObject(7, 66, abilityInfo.ability?.name!, TextStyle.SUMMARY_ALT); // TODO: is this bang correct?
|
||||
abilityInfo.nameText = addTextObject(7, 68, abilityInfo.ability?.name!, TextStyle.SUMMARY_ALT); // TODO: is this bang correct?
|
||||
abilityInfo.nameText.setOrigin(0, 1);
|
||||
profileContainer.add(abilityInfo.nameText);
|
||||
|
||||
abilityInfo.descriptionText = addTextObject(7, 69, abilityInfo.ability?.description!, TextStyle.WINDOW_ALT, {
|
||||
abilityInfo.descriptionText = addTextObject(7, 71, abilityInfo.ability?.description!, TextStyle.WINDOW_ALT, {
|
||||
wordWrap: { width: 1224 },
|
||||
}); // TODO: is this bang correct?
|
||||
abilityInfo.descriptionText.setOrigin(0, 0);
|
||||
@ -1000,16 +1000,16 @@ export default class SummaryUiHandler extends UiHandler {
|
||||
16 * rowIndex,
|
||||
statName,
|
||||
natureStatMultiplier === 1
|
||||
? TextStyle.SUMMARY
|
||||
? TextStyle.SUMMARY_STATS
|
||||
: natureStatMultiplier > 1
|
||||
? TextStyle.SUMMARY_PINK
|
||||
: TextStyle.SUMMARY_BLUE,
|
||||
? TextStyle.SUMMARY_STATS_PINK
|
||||
: TextStyle.SUMMARY_STATS_BLUE,
|
||||
);
|
||||
const ivLabel = addTextObject(
|
||||
115 * colIndex + (colIndex === 1 ? 5 : 0),
|
||||
16 * rowIndex,
|
||||
statName,
|
||||
this.pokemon?.ivs[stat] === 31 ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY,
|
||||
this.pokemon?.ivs[stat] === 31 ? TextStyle.SUMMARY_STATS_GOLD : TextStyle.SUMMARY_STATS,
|
||||
);
|
||||
|
||||
statLabel.setOrigin(0.5, 0);
|
||||
|
323
src/ui/text.ts
323
src/ui/text.ts
@ -11,27 +11,48 @@ export enum TextStyle {
|
||||
MESSAGE,
|
||||
WINDOW,
|
||||
WINDOW_ALT,
|
||||
WINDOW_BATTLE_COMMAND,
|
||||
BATTLE_INFO,
|
||||
PARTY,
|
||||
PARTY_RED,
|
||||
PARTY_CANCEL_BUTTON,
|
||||
INSTRUCTIONS_TEXT,
|
||||
MOVE_LABEL,
|
||||
SUMMARY,
|
||||
SUMMARY_DEX_NUM,
|
||||
SUMMARY_DEX_NUM_GOLD,
|
||||
SUMMARY_ALT,
|
||||
SUMMARY_HEADER,
|
||||
SUMMARY_RED,
|
||||
SUMMARY_BLUE,
|
||||
SUMMARY_PINK,
|
||||
SUMMARY_GOLD,
|
||||
SUMMARY_GRAY,
|
||||
SUMMARY_GREEN,
|
||||
SUMMARY_STATS,
|
||||
SUMMARY_STATS_BLUE,
|
||||
SUMMARY_STATS_PINK,
|
||||
SUMMARY_STATS_GOLD,
|
||||
LUCK_VALUE,
|
||||
STATS_HEXAGON,
|
||||
GROWTH_RATE_TYPE,
|
||||
MONEY, // Money default styling (pale yellow)
|
||||
MONEY_WINDOW, // Money displayed in Windows (needs different colors based on theme)
|
||||
HEADER_LABEL,
|
||||
STATS_LABEL,
|
||||
STATS_VALUE,
|
||||
SETTINGS_VALUE,
|
||||
SETTINGS_LABEL,
|
||||
SETTINGS_LABEL_NAVBAR,
|
||||
SETTINGS_SELECTED,
|
||||
SETTINGS_LOCKED,
|
||||
EGG_LIST,
|
||||
EGG_SUMMARY_NAME,
|
||||
EGG_SUMMARY_DEX,
|
||||
STARTER_VALUE_LIMIT,
|
||||
TOOLTIP_TITLE,
|
||||
TOOLTIP_CONTENT,
|
||||
FILTER_BAR_MAIN,
|
||||
MOVE_INFO_CONTENT,
|
||||
MOVE_PP_FULL,
|
||||
MOVE_PP_HALF_FULL,
|
||||
@ -73,10 +94,6 @@ export function addTextObject(
|
||||
ret.setLineSpacing(scale * 30);
|
||||
}
|
||||
|
||||
if (ret.lineSpacing < 12 && i18next.resolvedLanguage === "ja") {
|
||||
ret.setLineSpacing(ret.lineSpacing + 35);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -122,10 +139,6 @@ export function addBBCodeTextObject(
|
||||
ret.setLineSpacing(scale * 60);
|
||||
}
|
||||
|
||||
if (ret.lineSpacing < 12 && i18next.resolvedLanguage === "ja") {
|
||||
ret.setLineSpacing(ret.lineSpacing + 35);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -154,7 +167,7 @@ export function getTextStyleOptions(
|
||||
const lang = i18next.resolvedLanguage;
|
||||
let shadowXpos = 4;
|
||||
let shadowYpos = 5;
|
||||
let scale = 0.1666666667;
|
||||
const scale = 0.1666666667;
|
||||
const defaultFontSize = 96;
|
||||
|
||||
let styleOptions: Phaser.Types.GameObjects.Text.TextStyle = {
|
||||
@ -166,13 +179,58 @@ export function getTextStyleOptions(
|
||||
},
|
||||
};
|
||||
|
||||
if (i18next.resolvedLanguage === "ja") {
|
||||
scale = 0.1388888889;
|
||||
styleOptions.padding = { top: 2, bottom: 4 };
|
||||
}
|
||||
|
||||
switch (style) {
|
||||
case TextStyle.SUMMARY:
|
||||
case TextStyle.SUMMARY: {
|
||||
const fontSizeLabel = "96px";
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.padding = { top: 6, bottom: 4 };
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = fontSizeLabel;
|
||||
break;
|
||||
}
|
||||
// shadowXpos = 5;
|
||||
// shadowYpos = 5;
|
||||
// break;
|
||||
case TextStyle.SUMMARY_HEADER: {
|
||||
let fontSizeLabel = "96px";
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.padding = { bottom: 7 };
|
||||
fontSizeLabel = "80px";
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = fontSizeLabel;
|
||||
break;
|
||||
}
|
||||
// shadowXpos = 5;
|
||||
// shadowYpos = 5;
|
||||
// break;
|
||||
case TextStyle.SUMMARY_DEX_NUM: {
|
||||
const fontSizeLabel = "96px";
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.padding = { top: 2, bottom: 10 };
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = fontSizeLabel;
|
||||
shadowXpos = 5;
|
||||
shadowYpos = 5;
|
||||
break;
|
||||
}
|
||||
case TextStyle.SUMMARY_DEX_NUM_GOLD: {
|
||||
const fontSizeLabel = "96px";
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.padding = { top: 2, bottom: 10 };
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = fontSizeLabel;
|
||||
shadowXpos = 5;
|
||||
shadowYpos = 5;
|
||||
break;
|
||||
}
|
||||
case TextStyle.SUMMARY_ALT:
|
||||
case TextStyle.SUMMARY_BLUE:
|
||||
case TextStyle.SUMMARY_RED:
|
||||
@ -180,6 +238,10 @@ export function getTextStyleOptions(
|
||||
case TextStyle.SUMMARY_GOLD:
|
||||
case TextStyle.SUMMARY_GRAY:
|
||||
case TextStyle.SUMMARY_GREEN:
|
||||
case TextStyle.SUMMARY_STATS:
|
||||
case TextStyle.SUMMARY_STATS_BLUE:
|
||||
case TextStyle.SUMMARY_STATS_PINK:
|
||||
case TextStyle.SUMMARY_STATS_GOLD:
|
||||
case TextStyle.WINDOW:
|
||||
case TextStyle.WINDOW_ALT:
|
||||
case TextStyle.ME_OPTION_DEFAULT:
|
||||
@ -187,6 +249,43 @@ export function getTextStyleOptions(
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 3;
|
||||
break;
|
||||
case TextStyle.LUCK_VALUE: {
|
||||
const fontSizeLabel = "96px";
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.padding = { top: -6, bottom: 2 };
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = fontSizeLabel;
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 4;
|
||||
break;
|
||||
}
|
||||
case TextStyle.GROWTH_RATE_TYPE: {
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.padding = { left: 24 };
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = defaultFontSize - 30;
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 3;
|
||||
break;
|
||||
}
|
||||
case TextStyle.WINDOW_BATTLE_COMMAND: {
|
||||
let fontSizeLabel = "96px";
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.padding = { top: 2 };
|
||||
fontSizeLabel = "92px";
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = fontSizeLabel;
|
||||
break;
|
||||
}
|
||||
// shadowXpos = 5;
|
||||
// shadowYpos = 5;
|
||||
// break;
|
||||
case TextStyle.STATS_LABEL: {
|
||||
let fontSizeLabel = "96px";
|
||||
switch (lang) {
|
||||
@ -218,10 +317,76 @@ export function getTextStyleOptions(
|
||||
break;
|
||||
}
|
||||
case TextStyle.MESSAGE:
|
||||
case TextStyle.SETTINGS_LABEL:
|
||||
case TextStyle.SETTINGS_LOCKED:
|
||||
case TextStyle.SETTINGS_SELECTED:
|
||||
styleOptions.fontSize = defaultFontSize;
|
||||
break;
|
||||
case TextStyle.HEADER_LABEL:
|
||||
styleOptions.fontSize = defaultFontSize;
|
||||
styleOptions.padding = { top: 6 };
|
||||
break;
|
||||
case TextStyle.SETTINGS_VALUE:
|
||||
case TextStyle.SETTINGS_LABEL: {
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 3;
|
||||
let fontSizeValue = "96px";
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
fontSizeValue = "80px";
|
||||
styleOptions.padding = { top: 10 };
|
||||
break;
|
||||
default:
|
||||
fontSizeValue = "96px";
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = fontSizeValue;
|
||||
break;
|
||||
}
|
||||
case TextStyle.SETTINGS_LABEL_NAVBAR: {
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 3;
|
||||
let fontSizeValue = "96px";
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
fontSizeValue = "92px";
|
||||
break;
|
||||
default:
|
||||
fontSizeValue = "96px";
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = fontSizeValue;
|
||||
break;
|
||||
}
|
||||
case TextStyle.SETTINGS_LOCKED: {
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 3;
|
||||
let fontSizeValue = "96px";
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
fontSizeValue = "80px";
|
||||
styleOptions.padding = { top: 10 };
|
||||
break;
|
||||
default:
|
||||
fontSizeValue = "96px";
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = fontSizeValue;
|
||||
break;
|
||||
}
|
||||
case TextStyle.SETTINGS_SELECTED: {
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 3;
|
||||
let fontSizeValue = "96px";
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
fontSizeValue = "80px";
|
||||
styleOptions.padding = { top: 10 };
|
||||
break;
|
||||
default:
|
||||
fontSizeValue = "96px";
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = fontSizeValue;
|
||||
break;
|
||||
}
|
||||
case TextStyle.BATTLE_INFO:
|
||||
case TextStyle.MONEY:
|
||||
case TextStyle.MONEY_WINDOW:
|
||||
@ -231,11 +396,108 @@ export function getTextStyleOptions(
|
||||
shadowYpos = 3.5;
|
||||
break;
|
||||
case TextStyle.PARTY:
|
||||
case TextStyle.PARTY_RED:
|
||||
case TextStyle.PARTY_RED: {
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.padding = { top: -12, bottom: 4 };
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = defaultFontSize - 30;
|
||||
styleOptions.fontFamily = "pkmnems";
|
||||
break;
|
||||
case TextStyle.TOOLTIP_CONTENT:
|
||||
}
|
||||
case TextStyle.PARTY_CANCEL_BUTTON: {
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.fontSize = defaultFontSize - 42;
|
||||
styleOptions.padding = { top: 4 };
|
||||
break;
|
||||
default:
|
||||
styleOptions.fontSize = defaultFontSize - 30;
|
||||
styleOptions.padding = { left: 12 };
|
||||
break;
|
||||
}
|
||||
styleOptions.fontFamily = "pkmnems";
|
||||
break;
|
||||
}
|
||||
case TextStyle.INSTRUCTIONS_TEXT: {
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.padding = { top: -3, bottom: 4 };
|
||||
break;
|
||||
}
|
||||
styleOptions.fontSize = defaultFontSize - 30;
|
||||
styleOptions.fontFamily = "pkmnems";
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 3;
|
||||
break;
|
||||
}
|
||||
case TextStyle.MOVE_LABEL: {
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.fontSize = defaultFontSize - 16;
|
||||
styleOptions.padding = { top: -14, bottom: 8 };
|
||||
break;
|
||||
default:
|
||||
styleOptions.fontSize = defaultFontSize - 30;
|
||||
break;
|
||||
}
|
||||
styleOptions.fontFamily = "pkmnems";
|
||||
break;
|
||||
}
|
||||
case TextStyle.EGG_LIST:
|
||||
styleOptions.fontSize = defaultFontSize - 34;
|
||||
break;
|
||||
case TextStyle.EGG_SUMMARY_NAME: {
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.padding = { top: -1 };
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TextStyle.EGG_SUMMARY_DEX: {
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.padding = { top: 2 };
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TextStyle.STARTER_VALUE_LIMIT:
|
||||
styleOptions.fontSize = defaultFontSize - 36;
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 3;
|
||||
break;
|
||||
case TextStyle.TOOLTIP_CONTENT: {
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.fontSize = defaultFontSize - 44;
|
||||
styleOptions.padding = { top: 10, right: 10 };
|
||||
break;
|
||||
default:
|
||||
styleOptions.fontSize = defaultFontSize - 32;
|
||||
break;
|
||||
}
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 3;
|
||||
break;
|
||||
}
|
||||
case TextStyle.FILTER_BAR_MAIN: {
|
||||
switch (lang) {
|
||||
case "ja":
|
||||
styleOptions.fontSize = defaultFontSize - 48;
|
||||
styleOptions.padding = { top: 10, right: 10 };
|
||||
break;
|
||||
default:
|
||||
styleOptions.fontSize = defaultFontSize - 32;
|
||||
break;
|
||||
}
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 3;
|
||||
break;
|
||||
}
|
||||
case TextStyle.STATS_HEXAGON:
|
||||
styleOptions.fontSize = defaultFontSize - 32;
|
||||
shadowXpos = 3;
|
||||
shadowYpos = 3;
|
||||
@ -330,9 +592,14 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui
|
||||
case TextStyle.MESSAGE:
|
||||
return !shadow ? "#f8f8f8" : "#6b5a73";
|
||||
case TextStyle.WINDOW:
|
||||
case TextStyle.WINDOW_BATTLE_COMMAND:
|
||||
case TextStyle.MOVE_INFO_CONTENT:
|
||||
case TextStyle.STATS_HEXAGON:
|
||||
case TextStyle.MOVE_PP_FULL:
|
||||
case TextStyle.EGG_LIST:
|
||||
case TextStyle.TOOLTIP_CONTENT:
|
||||
case TextStyle.FILTER_BAR_MAIN:
|
||||
case TextStyle.STARTER_VALUE_LIMIT:
|
||||
case TextStyle.SETTINGS_VALUE:
|
||||
if (isLegacyTheme) {
|
||||
return !shadow ? "#484848" : "#d0d0c8";
|
||||
@ -361,12 +628,22 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui
|
||||
}
|
||||
return !shadow ? "#f8f8f8" : "#6b5a73";
|
||||
case TextStyle.PARTY:
|
||||
case TextStyle.PARTY_CANCEL_BUTTON:
|
||||
case TextStyle.INSTRUCTIONS_TEXT:
|
||||
case TextStyle.MOVE_LABEL:
|
||||
return !shadow ? "#f8f8f8" : "#707070";
|
||||
case TextStyle.PARTY_RED:
|
||||
return !shadow ? "#f89890" : "#984038";
|
||||
case TextStyle.SUMMARY:
|
||||
case TextStyle.SUMMARY_DEX_NUM:
|
||||
case TextStyle.SUMMARY_HEADER:
|
||||
case TextStyle.SUMMARY_STATS:
|
||||
case TextStyle.EGG_SUMMARY_NAME:
|
||||
case TextStyle.EGG_SUMMARY_DEX:
|
||||
case TextStyle.LUCK_VALUE:
|
||||
return !shadow ? "#f8f8f8" : "#636363";
|
||||
case TextStyle.SUMMARY_ALT:
|
||||
case TextStyle.GROWTH_RATE_TYPE:
|
||||
if (isLegacyTheme) {
|
||||
return !shadow ? "#f8f8f8" : "#636363";
|
||||
}
|
||||
@ -375,10 +652,14 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui
|
||||
case TextStyle.TOOLTIP_TITLE:
|
||||
return !shadow ? "#e70808" : "#ffbd73";
|
||||
case TextStyle.SUMMARY_BLUE:
|
||||
case TextStyle.SUMMARY_STATS_BLUE:
|
||||
return !shadow ? "#40c8f8" : "#006090";
|
||||
case TextStyle.SUMMARY_PINK:
|
||||
case TextStyle.SUMMARY_STATS_PINK:
|
||||
return !shadow ? "#f89890" : "#984038";
|
||||
case TextStyle.SUMMARY_GOLD:
|
||||
case TextStyle.SUMMARY_DEX_NUM_GOLD:
|
||||
case TextStyle.SUMMARY_STATS_GOLD:
|
||||
case TextStyle.MONEY:
|
||||
return !shadow ? "#e8e8a8" : "#a0a060"; // Pale Yellow/Gold
|
||||
case TextStyle.MONEY_WINDOW:
|
||||
@ -399,6 +680,8 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui
|
||||
case TextStyle.SUMMARY_GREEN:
|
||||
return !shadow ? "#78c850" : "#306850";
|
||||
case TextStyle.SETTINGS_LABEL:
|
||||
case TextStyle.SETTINGS_LABEL_NAVBAR:
|
||||
case TextStyle.HEADER_LABEL:
|
||||
case TextStyle.PERFECT_IV:
|
||||
return !shadow ? "#f8b050" : "#c07800";
|
||||
case TextStyle.SETTINGS_SELECTED:
|
||||
|
@ -341,6 +341,10 @@ export class NumberHolder {
|
||||
constructor(value: number) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
valueOf(): number {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
export class FixedInt {
|
||||
@ -349,6 +353,10 @@ export class FixedInt {
|
||||
constructor(value: number) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
[Symbol.toPrimitive](_hint: string): number {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
export function fixedInt(value: number): number {
|
||||
|
78
test/abilities/anger-point.test.ts
Normal file
78
test/abilities/anger-point.test.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { PostReceiveCritStatStageChangeAbAttr } from "#app/data/abilities/ability";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Ability - Anger Point", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.ability(AbilityId.BALL_FETCH)
|
||||
.battleStyle("single")
|
||||
.criticalHits(false)
|
||||
.enemySpecies(SpeciesId.MAGIKARP)
|
||||
.enemyAbility(AbilityId.BALL_FETCH)
|
||||
.enemyMoveset(MoveId.SPLASH)
|
||||
.enemyLevel(100);
|
||||
});
|
||||
|
||||
it("should set the user's attack stage to +6 when hit by a critical hit", async () => {
|
||||
game.override.enemyAbility(AbilityId.ANGER_POINT).moveset(MoveId.FALSE_SWIPE);
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
// minimize the enemy's attack stage to ensure it is always set to +6
|
||||
enemy.setStatStage(Stat.ATK, -6);
|
||||
vi.spyOn(enemy, "getCriticalHitResult").mockReturnValueOnce(true);
|
||||
game.move.select(MoveId.FALSE_SWIPE);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(enemy.getStatStage(Stat.ATK)).toBe(6);
|
||||
});
|
||||
|
||||
it("should only proc once when a multi-hit move crits on the first hit", async () => {
|
||||
game.override
|
||||
.moveset(MoveId.BULLET_SEED)
|
||||
.enemyLevel(50)
|
||||
.enemyAbility(AbilityId.ANGER_POINT)
|
||||
.ability(AbilityId.SKILL_LINK);
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(enemy, "getCriticalHitResult").mockReturnValueOnce(true);
|
||||
const angerPointSpy = vi.spyOn(PostReceiveCritStatStageChangeAbAttr.prototype, "apply");
|
||||
game.move.select(MoveId.BULLET_SEED);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(angerPointSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should set a contrary user's attack stage to -6 when hit by a critical hit", async () => {
|
||||
game.override
|
||||
.enemyAbility(AbilityId.ANGER_POINT)
|
||||
.enemyPassiveAbility(AbilityId.CONTRARY)
|
||||
.enemyHasPassiveAbility(true)
|
||||
.moveset(MoveId.FALSE_SWIPE);
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(enemy, "getCriticalHitResult").mockReturnValueOnce(true);
|
||||
enemy.setStatStage(Stat.ATK, 6);
|
||||
game.move.select(MoveId.FALSE_SWIPE);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(enemy.getStatStage(Stat.ATK)).toBe(-6);
|
||||
});
|
||||
});
|
@ -8,6 +8,7 @@ import { SpeciesId } from "#enums/species-id";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
describe("Abilities - Desolate Land", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -145,6 +146,7 @@ describe("Abilities - Desolate Land", () => {
|
||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN);
|
||||
|
||||
vi.spyOn(game.scene.getPlayerPokemon()!, "randBattleSeedInt").mockReturnValue(0);
|
||||
vi.spyOn(globalScene, "randBattleSeedInt").mockReturnValue(0);
|
||||
|
||||
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
@ -45,7 +45,7 @@ describe("Abilities - Good As Gold", () => {
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(MoveId.SPLASH, 0);
|
||||
game.move.select(MoveId.SPLASH);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
@ -54,12 +54,13 @@ describe("Abilities - Good As Gold", () => {
|
||||
});
|
||||
|
||||
it("should block memento and prevent the user from fainting", async () => {
|
||||
game.override.enemyMoveset([MoveId.MEMENTO]);
|
||||
game.override.enemyAbility(AbilityId.GOOD_AS_GOLD);
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
game.move.select(MoveId.MEMENTO);
|
||||
|
||||
game.move.use(MoveId.MEMENTO);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(game.scene.getPlayerPokemon()!.isFainted()).toBe(false);
|
||||
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(0);
|
||||
expect(game.field.getPlayerPokemon().isFainted()).toBe(false);
|
||||
expect(game.field.getEnemyPokemon().getStatStage(Stat.ATK)).toBe(0);
|
||||
});
|
||||
|
||||
it("should not block any status moves that target the field, one side, or all pokemon", async () => {
|
||||
|
@ -28,8 +28,10 @@ describe("Abilities - Gorilla Tactics", () => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleStyle("single")
|
||||
.criticalHits(false)
|
||||
.enemyAbility(AbilityId.BALL_FETCH)
|
||||
.enemySpecies(SpeciesId.MAGIKARP)
|
||||
.enemyMoveset(MoveId.SPLASH)
|
||||
.enemyLevel(30)
|
||||
.moveset([MoveId.SPLASH, MoveId.TACKLE, MoveId.GROWL, MoveId.METRONOME])
|
||||
.ability(AbilityId.GORILLA_TACTICS);
|
||||
@ -42,7 +44,6 @@ describe("Abilities - Gorilla Tactics", () => {
|
||||
const initialAtkStat = darmanitan.getStat(Stat.ATK);
|
||||
|
||||
game.move.select(MoveId.SPLASH);
|
||||
await game.move.forceEnemyMove(MoveId.SPLASH);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expect(darmanitan.getStat(Stat.ATK, false)).toBeCloseTo(initialAtkStat * 1.5);
|
||||
@ -59,7 +60,6 @@ describe("Abilities - Gorilla Tactics", () => {
|
||||
|
||||
// First turn, lock move to Growl
|
||||
game.move.select(MoveId.GROWL);
|
||||
await game.move.forceEnemyMove(MoveId.SPLASH);
|
||||
await game.toNextTurn();
|
||||
|
||||
// Second turn, Growl is interrupted by Disable
|
||||
@ -72,7 +72,7 @@ describe("Abilities - Gorilla Tactics", () => {
|
||||
|
||||
// Third turn, Struggle is used
|
||||
game.move.select(MoveId.TACKLE);
|
||||
await game.move.forceEnemyMove(MoveId.SPLASH); //prevent protect from being used by the enemy
|
||||
await game.move.forceEnemyMove(MoveId.SPLASH); // prevent disable from being used by the enemy
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
|
||||
@ -106,11 +106,13 @@ describe("Abilities - Gorilla Tactics", () => {
|
||||
const darmanitan = game.field.getPlayerPokemon();
|
||||
|
||||
game.move.select(MoveId.TACKLE);
|
||||
await game.move.selectEnemyMove(MoveId.PROTECT);
|
||||
await game.move.forceEnemyMove(MoveId.PROTECT);
|
||||
|
||||
await game.toEndOfTurn();
|
||||
expect(darmanitan.isMoveRestricted(MoveId.SPLASH)).toBe(true);
|
||||
expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(false);
|
||||
const enemy = game.field.getEnemyPokemon();
|
||||
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||
});
|
||||
|
||||
it("should activate when a move is succesfully executed but misses", async () => {
|
||||
@ -119,7 +121,6 @@ describe("Abilities - Gorilla Tactics", () => {
|
||||
const darmanitan = game.field.getPlayerPokemon();
|
||||
|
||||
game.move.select(MoveId.TACKLE);
|
||||
await game.move.selectEnemyMove(MoveId.SPLASH);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.move.forceMiss();
|
||||
await game.toEndOfTurn();
|
||||
|
@ -6,6 +6,7 @@ import { SpeciesId } from "#enums/species-id";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import Overrides from "#app/overrides";
|
||||
|
||||
describe("Abilities - Honey Gather", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -63,6 +64,8 @@ describe("Abilities - Honey Gather", () => {
|
||||
// something weird is going on with the test framework, so this is required to prevent a crash
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(enemy, "scene", "get").mockReturnValue(game.scene);
|
||||
// Expects next wave so run must succeed
|
||||
vi.spyOn(Overrides, "RUN_SUCCESS_OVERRIDE", "get").mockReturnValue(true);
|
||||
|
||||
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
@ -259,7 +259,7 @@ describe("Abilities - Ice Face", () => {
|
||||
|
||||
const eiscue = game.scene.getEnemyPokemon()!;
|
||||
|
||||
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
|
||||
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeDefined();
|
||||
expect(eiscue.formIndex).toBe(icefaceForm);
|
||||
expect(eiscue.hasAbility(AbilityId.ICE_FACE)).toBe(true);
|
||||
});
|
||||
@ -269,13 +269,9 @@ describe("Abilities - Ice Face", () => {
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
|
||||
game.move.select(MoveId.SIMPLE_BEAM);
|
||||
|
||||
await game.phaseInterceptor.to(TurnInitPhase);
|
||||
|
||||
const eiscue = game.scene.getEnemyPokemon()!;
|
||||
|
||||
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
|
||||
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeDefined();
|
||||
expect(eiscue.formIndex).toBe(icefaceForm);
|
||||
expect(game.scene.getPlayerPokemon()!.hasAbility(AbilityId.TRACE)).toBe(true);
|
||||
});
|
||||
|
@ -74,9 +74,8 @@ describe("Abilities - Imposter", () => {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: this doesn't actually test imposter - transforming happens before poewr split
|
||||
it("should copy in-battle overridden stats", async () => {
|
||||
game.override.enemyMoveset([MoveId.POWER_SPLIT]);
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
@ -85,7 +84,8 @@ describe("Abilities - Imposter", () => {
|
||||
const avgAtk = Math.floor((player.getStat(Stat.ATK, false) + enemy.getStat(Stat.ATK, false)) / 2);
|
||||
const avgSpAtk = Math.floor((player.getStat(Stat.SPATK, false) + enemy.getStat(Stat.SPATK, false)) / 2);
|
||||
|
||||
game.move.select(MoveId.TACKLE);
|
||||
game.move.use(MoveId.SPLASH);
|
||||
await game.move.forceEnemyMove(MoveId.POWER_SPLIT);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(player.getStat(Stat.ATK, false)).toBe(avgAtk);
|
||||
@ -101,9 +101,6 @@ describe("Abilities - Imposter", () => {
|
||||
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(MoveId.TACKLE);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
player.getMoveset().forEach(move => {
|
||||
// Should set correct maximum PP without touching `ppUp`
|
||||
if (move) {
|
||||
@ -122,15 +119,10 @@ describe("Abilities - Imposter", () => {
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
||||
|
||||
game.move.select(MoveId.TACKLE);
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
|
||||
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(-1);
|
||||
});
|
||||
|
||||
it("should persist transformed attributes across reloads", async () => {
|
||||
game.override.moveset([MoveId.ABSORB]);
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
@ -162,7 +154,7 @@ describe("Abilities - Imposter", () => {
|
||||
});
|
||||
|
||||
it("should stay transformed with the correct form after reload", async () => {
|
||||
game.override.moveset([MoveId.ABSORB]).enemySpecies(SpeciesId.UNOWN);
|
||||
game.override.enemySpecies(SpeciesId.UNOWN);
|
||||
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
||||
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
@ -3,7 +3,6 @@ import Phaser from "phaser";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { getMovePosition } from "#test/testUtils/gameManagerUtils";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
@ -114,7 +113,7 @@ describe("Abilities - Intimidate", () => {
|
||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
|
||||
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
|
||||
|
||||
game.move.select(getMovePosition(game.scene, 0, MoveId.SPLASH));
|
||||
game.move.select(MoveId.SPLASH);
|
||||
await game.toNextTurn();
|
||||
|
||||
enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
@ -36,10 +36,8 @@ describe("Abilities - Lightningrod", () => {
|
||||
it("should redirect electric type moves", async () => {
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
||||
|
||||
const enemy1 = game.scene.getEnemyField()[0];
|
||||
const enemy2 = game.scene.getEnemyField()[1];
|
||||
|
||||
enemy2.summonData.ability = AbilityId.LIGHTNING_ROD;
|
||||
const [enemy1, enemy2] = game.scene.getEnemyField();
|
||||
game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD);
|
||||
|
||||
game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||
@ -52,10 +50,8 @@ describe("Abilities - Lightningrod", () => {
|
||||
game.override.moveset([MoveId.SPLASH, MoveId.AERIAL_ACE]);
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
||||
|
||||
const enemy1 = game.scene.getEnemyField()[0];
|
||||
const enemy2 = game.scene.getEnemyField()[1];
|
||||
|
||||
enemy2.summonData.ability = AbilityId.LIGHTNING_ROD;
|
||||
const [enemy1, enemy2] = game.scene.getEnemyField();
|
||||
game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD);
|
||||
|
||||
game.move.select(MoveId.AERIAL_ACE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||
@ -68,8 +64,7 @@ describe("Abilities - Lightningrod", () => {
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
||||
|
||||
const enemy2 = game.scene.getEnemyField()[1];
|
||||
|
||||
enemy2.summonData.ability = AbilityId.LIGHTNING_ROD;
|
||||
game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD);
|
||||
|
||||
game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||
@ -81,31 +76,25 @@ describe("Abilities - Lightningrod", () => {
|
||||
|
||||
it("should not redirect moves changed from electric type via ability", async () => {
|
||||
game.override.ability(AbilityId.NORMALIZE);
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||
|
||||
const enemy1 = game.scene.getEnemyField()[0];
|
||||
const enemy2 = game.scene.getEnemyField()[1];
|
||||
|
||||
enemy2.summonData.ability = AbilityId.LIGHTNING_ROD;
|
||||
const [enemy1, enemy2] = game.scene.getEnemyField();
|
||||
game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD);
|
||||
|
||||
game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(enemy1.isFullHp()).toBe(false);
|
||||
});
|
||||
|
||||
it("should redirect moves changed to electric type via ability", async () => {
|
||||
game.override.ability(AbilityId.GALVANIZE).moveset(MoveId.TACKLE);
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
||||
game.override.ability(AbilityId.GALVANIZE);
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||
|
||||
const enemy1 = game.scene.getEnemyField()[0];
|
||||
const enemy2 = game.scene.getEnemyField()[1];
|
||||
const [enemy1, enemy2] = game.scene.getEnemyField();
|
||||
game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD);
|
||||
|
||||
enemy2.summonData.ability = AbilityId.LIGHTNING_ROD;
|
||||
|
||||
game.move.select(MoveId.TACKLE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||
game.move.use(MoveId.TACKLE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(enemy1.isFullHp()).toBe(true);
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
@ -24,29 +22,28 @@ describe("Abilities - Mold Breaker", () => {
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([MoveId.SPLASH])
|
||||
.ability(AbilityId.MOLD_BREAKER)
|
||||
.battleStyle("single")
|
||||
.criticalHits(false)
|
||||
.enemySpecies(SpeciesId.MAGIKARP)
|
||||
.enemyAbility(AbilityId.BALL_FETCH)
|
||||
.enemyAbility(AbilityId.STURDY)
|
||||
.enemyMoveset(MoveId.SPLASH);
|
||||
});
|
||||
|
||||
it("should turn off the ignore abilities arena variable after the user's move", async () => {
|
||||
game.override
|
||||
.enemyMoveset(MoveId.SPLASH)
|
||||
.ability(AbilityId.MOLD_BREAKER)
|
||||
.moveset([MoveId.ERUPTION])
|
||||
.startingLevel(100)
|
||||
.enemyLevel(2);
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
await game.classicMode.startBattle([SpeciesId.PINSIR]);
|
||||
|
||||
expect(enemy.isFainted()).toBe(false);
|
||||
game.move.select(MoveId.SPLASH);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.phaseInterceptor.to("MoveEndPhase", true);
|
||||
expect(globalScene.arena.ignoreAbilities).toBe(false);
|
||||
const player = game.field.getPlayerPokemon();
|
||||
const enemy = game.field.getEnemyPokemon();
|
||||
|
||||
game.move.use(MoveId.X_SCISSOR);
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
expect(game.scene.arena.ignoreAbilities).toBe(true);
|
||||
expect(game.scene.arena.ignoringEffectSource).toBe(player.getBattlerIndex());
|
||||
|
||||
await game.toEndOfTurn();
|
||||
expect(game.scene.arena.ignoreAbilities).toBe(false);
|
||||
expect(enemy.isFainted()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
@ -65,8 +65,7 @@ describe("Abilities - Moxie", () => {
|
||||
|
||||
secondPokemon.hp = 1;
|
||||
|
||||
game.move.select(moveToUse);
|
||||
game.selectTarget(BattlerIndex.PLAYER_2);
|
||||
game.move.select(moveToUse, BattlerIndex.PLAYER_2);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
|
@ -10,6 +10,7 @@ import { Stat } from "#enums/stat";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
describe("Abilities - Neutralizing Gas", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -164,6 +165,7 @@ describe("Abilities - Neutralizing Gas", () => {
|
||||
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined();
|
||||
|
||||
vi.spyOn(game.scene.getPlayerPokemon()!, "randBattleSeedInt").mockReturnValue(0);
|
||||
vi.spyOn(globalScene, "randBattleSeedInt").mockReturnValue(0);
|
||||
|
||||
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
@ -193,6 +193,7 @@ describe("Abilities - Parental Bond", () => {
|
||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||
});
|
||||
|
||||
// TODO: consolidate all these tests into 1 block
|
||||
it("should only trigger post-target move effects once", async () => {
|
||||
game.override.moveset([MoveId.MIND_BLOWN]);
|
||||
|
||||
@ -233,42 +234,6 @@ describe("Abilities - Parental Bond", () => {
|
||||
expect(leadPokemon.isOfType(PokemonType.FIRE)).toBe(false);
|
||||
});
|
||||
|
||||
it("Moves boosted by this ability and Multi-Lens should strike 3 times", async () => {
|
||||
game.override.moveset([MoveId.TACKLE]).startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(MoveId.TACKLE);
|
||||
|
||||
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||
|
||||
expect(leadPokemon.turnData.hitCount).toBe(3);
|
||||
});
|
||||
|
||||
it("Seismic Toss boosted by this ability and Multi-Lens should strike 3 times", async () => {
|
||||
game.override.moveset([MoveId.SEISMIC_TOSS]).startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
const enemyStartingHp = enemyPokemon.hp;
|
||||
|
||||
game.move.select(MoveId.SEISMIC_TOSS);
|
||||
await game.move.forceHit();
|
||||
|
||||
await game.phaseInterceptor.to("DamageAnimPhase");
|
||||
|
||||
expect(leadPokemon.turnData.hitCount).toBe(3);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEndPhase", false);
|
||||
|
||||
expect(enemyPokemon.hp).toBe(enemyStartingHp - 200);
|
||||
});
|
||||
|
||||
it("Hyper Beam boosted by this ability should strike twice, then recharge", async () => {
|
||||
game.override.moveset([MoveId.HYPER_BEAM]);
|
||||
|
||||
|
@ -28,11 +28,11 @@ describe("Abilities - Screen Cleaner", () => {
|
||||
});
|
||||
|
||||
it("removes Aurora Veil", async () => {
|
||||
game.override.moveset([MoveId.HAIL]).enemyMoveset(MoveId.AURORA_VEIL);
|
||||
game.override.enemyMoveset(MoveId.AURORA_VEIL);
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
||||
|
||||
game.move.select(MoveId.HAIL);
|
||||
game.move.use(MoveId.HAIL);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(game.scene.arena.getTag(ArenaTagType.AURORA_VEIL)).toBeDefined();
|
||||
@ -49,7 +49,7 @@ describe("Abilities - Screen Cleaner", () => {
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
||||
|
||||
game.move.select(MoveId.SPLASH);
|
||||
game.move.use(MoveId.SPLASH);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(game.scene.arena.getTag(ArenaTagType.LIGHT_SCREEN)).toBeDefined();
|
||||
@ -66,7 +66,7 @@ describe("Abilities - Screen Cleaner", () => {
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
||||
|
||||
game.move.select(MoveId.SPLASH);
|
||||
game.move.use(MoveId.SPLASH);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(game.scene.arena.getTag(ArenaTagType.REFLECT)).toBeDefined();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user