mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-09 08:59:29 +02:00
Merge branch 'beta' into WorkingDiscardFunction
This commit is contained in:
commit
dc00b26664
1
.github/workflows/github-pages.yml
vendored
1
.github/workflows/github-pages.yml
vendored
@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
5
.github/workflows/linting.yml
vendored
5
.github/workflows/linting.yml
vendored
@ -41,4 +41,7 @@ jobs:
|
||||
run: pnpm biome-ci
|
||||
|
||||
- name: Check dependencies with depcruise
|
||||
run: pnpm depcruise
|
||||
run: pnpm depcruise
|
||||
|
||||
- name: Lint with ls-lint
|
||||
run: pnpm ls-lint
|
2
.github/workflows/test-shard-template.yml
vendored
2
.github/workflows/test-shard-template.yml
vendored
@ -44,4 +44,4 @@ jobs:
|
||||
run: pnpm i
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm exec vitest --project ${{ inputs.project }} --no-isolate --shard=${{ inputs.shard }}/${{ inputs.totalShards }} ${{ !runner.debug && '--silent' || '' }}
|
||||
run: pnpm test:silent --shard=${{ inputs.shard }}/${{ inputs.totalShards }}
|
||||
|
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
@ -11,6 +11,7 @@ on:
|
||||
- beta
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-path-change-filter:
|
||||
|
28
.ls-lint.yml
Normal file
28
.ls-lint.yml
Normal file
@ -0,0 +1,28 @@
|
||||
# Base settings to use
|
||||
# Note that the `_cfg` key isn't part of ls-lint's configuration, it's just a YAML anchor for reuse.
|
||||
_cfg: &cfg
|
||||
.ps1: kebab-case
|
||||
.ts: kebab-case
|
||||
.js: kebab-case
|
||||
.*.ts: kebab-case
|
||||
.*.js: kebab-case
|
||||
.dir: kebab-case
|
||||
.py: snake_case # python files should always use snake_case
|
||||
|
||||
ls:
|
||||
<<: *cfg
|
||||
src: &src
|
||||
<<: *cfg
|
||||
.dir: kebab-case | regex:@types
|
||||
.js: exists:0
|
||||
src/system/version-migration/versions:
|
||||
.ts: snake_case
|
||||
<<: *cfg
|
||||
test: *src
|
||||
ignore:
|
||||
- node_modules
|
||||
- .vscode
|
||||
- .github
|
||||
- .git
|
||||
- public
|
||||
- dist
|
@ -104,7 +104,7 @@ Most non-trivial changes (*especially bug fixes*) should come along with new tes
|
||||
- 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.
|
||||
|
||||
## 😈 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).
|
||||
> 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/test-utils/saves/everything.psrv) with _everything_ unlocked (even ones not legitimately obtainable, like unimplemented variant shinies).
|
||||
|
||||
1. Start the game up locally and navigate to `Menu -> Manage Data -> Import Data`
|
||||
2. Select [everything.prsv](test/testUtils/saves/everything.prsv) (`test/testUtils/saves/everything.prsv`) and confirm.
|
||||
2. Select [everything.prsv](test/test-utils/saves/everything.prsv) (`test/test-utils/saves/everything.prsv`) and confirm.
|
||||
|
@ -177,9 +177,10 @@
|
||||
}
|
||||
},
|
||||
|
||||
// Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes)
|
||||
// Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes),
|
||||
// as well as in all TS files in `scripts/` (which are assumed to be boilerplate templates).
|
||||
{
|
||||
"includes": ["**/src/overrides.ts", "**/src/enums/**/*"],
|
||||
"includes": ["**/src/overrides.ts", "**/src/enums/**/*", "**/scripts/**/*.ts"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"correctness": {
|
||||
@ -189,7 +190,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"includes": ["**/src/overrides.ts"],
|
||||
"includes": ["**/src/overrides.ts", "**/scripts/**/*.ts"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
|
2
global.d.ts
vendored
2
global.d.ts
vendored
@ -8,7 +8,7 @@ declare global {
|
||||
* Can technically be undefined/null but for ease of use we are going to assume it is always defined.
|
||||
* Used to load i18n files exclusively.
|
||||
*
|
||||
* To set up your own server in a test see `game_data.test.ts`
|
||||
* To set up your own server in a test see `game-data.test.ts`
|
||||
*/
|
||||
var server: SetupServerApi;
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
pre-commit:
|
||||
skip:
|
||||
- merge
|
||||
- rebase
|
||||
commands:
|
||||
biome-lint:
|
||||
run: pnpm exec biome check --write --reporter=summary --staged --no-errors-on-unmatched
|
||||
stage_fixed: true
|
||||
skip:
|
||||
- merge
|
||||
- rebase
|
||||
ls-lint:
|
||||
run: pnpm exec ls-lint
|
||||
|
||||
post-merge:
|
||||
commands:
|
||||
|
@ -12,7 +12,7 @@
|
||||
"test": "vitest run --no-isolate",
|
||||
"test:cov": "vitest run --coverage --no-isolate",
|
||||
"test:watch": "vitest watch --coverage --no-isolate",
|
||||
"test:silent": "vitest run --silent --no-isolate",
|
||||
"test:silent": "vitest run --silent='passed-only' --no-isolate",
|
||||
"test:create": "node scripts/create-test/create-test.js",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"eslint": "eslint --fix .",
|
||||
@ -28,9 +28,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.0",
|
||||
"@ls-lint/ls-lint": "2.3.1",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^22.16.3",
|
||||
"@vitest/coverage-istanbul": "^3.2.4",
|
||||
"@vitest/expect": "^3.2.4",
|
||||
"chalk": "^5.4.1",
|
||||
"dependency-cruiser": "^16.10.4",
|
||||
"inquirer": "^12.7.0",
|
||||
|
@ -45,6 +45,9 @@ importers:
|
||||
'@biomejs/biome':
|
||||
specifier: 2.0.0
|
||||
version: 2.0.0
|
||||
'@ls-lint/ls-lint':
|
||||
specifier: 2.3.1
|
||||
version: 2.3.1
|
||||
'@types/jsdom':
|
||||
specifier: ^21.1.7
|
||||
version: 21.1.7
|
||||
@ -54,6 +57,9 @@ importers:
|
||||
'@vitest/coverage-istanbul':
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(vitest@3.2.4(@types/node@22.16.3)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(yaml@2.8.0))
|
||||
'@vitest/expect':
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4
|
||||
chalk:
|
||||
specifier: ^5.4.1
|
||||
version: 5.4.1
|
||||
@ -565,6 +571,12 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.29':
|
||||
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
|
||||
|
||||
'@ls-lint/ls-lint@2.3.1':
|
||||
resolution: {integrity: sha512-vPe6IDByQnQRTxcAYjTxrmga/tSIui50VBFTB5KIJWY3OOFmxE2VtymjeSEfQfiMbhZV/ZPAqYy2lt8pZFQ0Rw==}
|
||||
cpu: [x64, arm64, s390x, ppc64le]
|
||||
os: [darwin, linux, win32]
|
||||
hasBin: true
|
||||
|
||||
'@material/material-color-utilities@0.2.7':
|
||||
resolution: {integrity: sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ==}
|
||||
|
||||
@ -2452,6 +2464,8 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.4
|
||||
|
||||
'@ls-lint/ls-lint@2.3.1': {}
|
||||
|
||||
'@material/material-color-utilities@0.2.7': {}
|
||||
|
||||
'@mswjs/interceptors@0.39.2':
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 362b2c4fcc20b31a7be6c2dab537055fbaeb247f
|
||||
Subproject commit e2fbba17ea7a96068970ea98a8a84ed3e25b6f07
|
@ -1,7 +1,7 @@
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { GameManager } from "#test/testUtils/gameManager";
|
||||
import { GameManager } from "#test/test-utils/game-manager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
@ -17,15 +17,20 @@ const version = "2.0.1";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const projectRoot = path.join(__dirname, "..", "..");
|
||||
const boilerplateFilePath = path.join(__dirname, "test-boilerplate.ts");
|
||||
const choices = [
|
||||
{ label: "Move", dir: "moves" },
|
||||
{ label: "Ability", dir: "abilities" },
|
||||
{ label: "Item", dir: "items" },
|
||||
{ label: "Mystery Encounter", dir: "mystery-encounter/encounters" },
|
||||
{ label: "Utils", dir: "utils" },
|
||||
{ label: "UI", dir: "ui" },
|
||||
];
|
||||
|
||||
const choices = /** @type {const} */ (["Move", "Ability", "Item", "Reward", "Mystery Encounter", "Utils", "UI"]);
|
||||
/** @typedef {choices[number]} choiceType */
|
||||
|
||||
/** @satisfies {{[k in choiceType]: string}} */
|
||||
const choicesToDirs = /** @type {const} */ ({
|
||||
Move: "moves",
|
||||
Ability: "abilities",
|
||||
Item: "items",
|
||||
Reward: "rewards",
|
||||
"Mystery Encounter": "mystery-encounter/encounters",
|
||||
Utils: "utils",
|
||||
UI: "ui",
|
||||
});
|
||||
|
||||
//#endregion
|
||||
//#region Functions
|
||||
@ -41,46 +46,47 @@ function getTestFolderPath(...folders) {
|
||||
|
||||
/**
|
||||
* Prompts the user to select a type via list.
|
||||
* @returns {Promise<{selectedOption: {label: string, dir: string}}>} the selected type
|
||||
* @returns {Promise<choiceType>} the selected type
|
||||
*/
|
||||
async function promptTestType() {
|
||||
const typeAnswer = await inquirer.prompt([
|
||||
{
|
||||
type: "list",
|
||||
name: "selectedOption",
|
||||
message: "What type of test would you like to create?",
|
||||
choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"],
|
||||
},
|
||||
]);
|
||||
/** @type {choiceType | "EXIT"} */
|
||||
const choice = await inquirer
|
||||
.prompt([
|
||||
{
|
||||
type: "list",
|
||||
name: "selectedOption",
|
||||
message: "What type of test would you like to create?",
|
||||
choices: [...choices, "EXIT"],
|
||||
},
|
||||
])
|
||||
.then(ta => ta.selectedOption);
|
||||
|
||||
if (typeAnswer.selectedOption === "EXIT") {
|
||||
if (choice === "EXIT") {
|
||||
console.log("Exiting...");
|
||||
return process.exit();
|
||||
}
|
||||
if (!choices.some(choice => choice.dir === typeAnswer.selectedOption.dir)) {
|
||||
console.error(`Please provide a valid type: (${choices.map(choice => choice.label).join(", ")})!`);
|
||||
return await promptTestType();
|
||||
return process.exit(0);
|
||||
}
|
||||
|
||||
return typeAnswer;
|
||||
return choice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user to provide a file name.
|
||||
* @param {string} selectedType
|
||||
* @returns {Promise<{userInput: string}>} the selected file name
|
||||
* @param {choiceType} selectedType The chosen string (used to display console logs)
|
||||
* @returns {Promise<string>} the selected file name
|
||||
*/
|
||||
async function promptFileName(selectedType) {
|
||||
/** @type {{userInput: string}} */
|
||||
const fileNameAnswer = await inquirer.prompt([
|
||||
{
|
||||
type: "input",
|
||||
name: "userInput",
|
||||
message: `Please provide the name of the ${selectedType}:`,
|
||||
},
|
||||
]);
|
||||
/** @type {string} */
|
||||
const fileNameAnswer = await inquirer
|
||||
.prompt([
|
||||
{
|
||||
type: "input",
|
||||
name: "userInput",
|
||||
message: `Please provide the name of the ${selectedType}.`,
|
||||
},
|
||||
])
|
||||
.then(fa => fa.userInput);
|
||||
|
||||
if (!fileNameAnswer.userInput || fileNameAnswer.userInput.trim().length === 0) {
|
||||
if (fileNameAnswer.trim().length === 0) {
|
||||
console.error("Please provide a valid file name!");
|
||||
return await promptFileName(selectedType);
|
||||
}
|
||||
@ -88,51 +94,66 @@ async function promptFileName(selectedType) {
|
||||
return fileNameAnswer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the path to the boilerplate file based on the current option.
|
||||
* @param {choiceType} choiceType The choice selected
|
||||
* @returns {string} The path to the boilerplate file
|
||||
*/
|
||||
function getBoilerplatePath(choiceType) {
|
||||
switch (choiceType) {
|
||||
// case "Reward":
|
||||
// return path.join(__dirname, "boilerplates/reward.ts");
|
||||
default:
|
||||
return path.join(__dirname, "boilerplates/default.ts");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the interactive test:create "CLI"
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function runInteractive() {
|
||||
console.group(chalk.grey(`Create Test - v${version}\n`));
|
||||
console.group(chalk.grey(`🧪 Create Test - v${version}\n`));
|
||||
|
||||
try {
|
||||
const typeAnswer = await promptTestType();
|
||||
const fileNameAnswer = await promptFileName(typeAnswer.selectedOption.label);
|
||||
const choice = await promptTestType();
|
||||
const fileNameAnswer = await promptFileName(choice);
|
||||
|
||||
const type = typeAnswer.selectedOption;
|
||||
// Convert fileName from snake_case or camelCase to kebab-case
|
||||
const fileName = fileNameAnswer.userInput
|
||||
const fileName = fileNameAnswer
|
||||
.replace(/_+/g, "-") // Convert snake_case (underscore) to kebab-case (dashes)
|
||||
.replace(/([a-z])([A-Z])/g, "$1-$2") // Convert camelCase to kebab-case
|
||||
.replace(/\s+/g, "-") // Replace spaces with dashes
|
||||
.toLowerCase(); // Ensure all lowercase
|
||||
// Format the description for the test case
|
||||
|
||||
// Format the description for the test case in Title Case
|
||||
const formattedName = fileName.replace(/-/g, " ").replace(/\b\w/g, char => char.toUpperCase());
|
||||
const description = `${choice} - ${formattedName}`;
|
||||
|
||||
// Determine the directory based on the type
|
||||
const dir = getTestFolderPath(type.dir);
|
||||
const description = `${type.label} - ${formattedName}`;
|
||||
const localDir = choicesToDirs[choice];
|
||||
const absoluteDir = getTestFolderPath(localDir);
|
||||
|
||||
// Define the content template
|
||||
const content = fs.readFileSync(boilerplateFilePath, "utf8").replace("{{description}}", description);
|
||||
const content = fs.readFileSync(getBoilerplatePath(choice), "utf8").replace("{{description}}", description);
|
||||
|
||||
// Ensure the directory exists
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
if (!fs.existsSync(absoluteDir)) {
|
||||
fs.mkdirSync(absoluteDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Create the file with the given name
|
||||
const filePath = path.join(dir, `${fileName}.test.ts`);
|
||||
const filePath = path.join(absoluteDir, `${fileName}.test.ts`);
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.error(chalk.red.bold(`\n✗ File "${fileName}.test.ts" already exists!\n`));
|
||||
console.error(chalk.red.bold(`✗ File "${fileName}.test.ts" already exists!\n`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Write the template content to the file
|
||||
fs.writeFileSync(filePath, content, "utf8");
|
||||
|
||||
console.log(chalk.green.bold(`\n✔ File created at: test/${type.dir}/${fileName}.test.ts\n`));
|
||||
console.log(chalk.green.bold(`✔ File created at: test/${localDir}/${fileName}.test.ts\n`));
|
||||
console.groupEnd();
|
||||
} catch (err) {
|
||||
console.error(chalk.red("✗ Error: ", err.message));
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
// biome-ignore lint/performance/noNamespaceImport: This is how you import fs from node
|
||||
import * as fs from "node:fs";
|
||||
import { AES, enc } from "crypto-js";
|
||||
import crypto_js from "crypto-js";
|
||||
|
||||
const { AES, enc } = crypto_js;
|
||||
|
||||
const SAVE_KEY = "x0i2O7WRiANTqPmZ";
|
||||
|
||||
@ -144,7 +146,7 @@ function main() {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
writeToFile(destPath, decrypt);
|
||||
writeToFile(args[1], decrypt);
|
||||
}
|
||||
|
||||
main();
|
||||
|
@ -2,13 +2,13 @@ import re
|
||||
|
||||
filenames = [['src/enums/moves.ts', 'move'], ['src/enums/abilities.ts', 'ability'], ['src/enums/species.ts', 'Pokémon']]
|
||||
|
||||
commentBlockStart = re.compile('\/\*[^\*].*') # Regex for the start of a comment block
|
||||
commentBlockEnd = re.compile('.*,\*\/') # Regex for the end of a comment block
|
||||
commentBlockStart = re.compile(r'\/\*[^\*].*') # Regex for the start of a comment block
|
||||
commentBlockEnd = re.compile(r'.*,\*\/') # Regex for the end of a comment block
|
||||
|
||||
commentExp = re.compile('(?:\/\*\*.*\*\/)') # Regex for a url comment that already existed in the file
|
||||
commentExp = re.compile(r'(?:\/\*\*.*\*\/)') # Regex for a url comment that already existed in the file
|
||||
enumExp = re.compile('.*,') # Regex for a regular enum line
|
||||
|
||||
numberExp = re.compile(' +\= +\d+,')
|
||||
numberExp = re.compile(r' +\= +\d+,')
|
||||
|
||||
replaceList = ['ALOLA', 'ETERNAL', 'GALAR', 'HISUI', 'PALDEA', 'BLOODMOON']
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { UserInfo } from "#types/UserInfo";
|
||||
import type { UserInfo } from "#types/user-info";
|
||||
|
||||
export interface AccountInfoResponse extends UserInfo {}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export class UpdateSessionSavedataRequest {
|
||||
export interface UpdateSessionSavedataRequest {
|
||||
slot: number;
|
||||
trainerId: number;
|
||||
secretId: number;
|
@ -4,7 +4,7 @@ export interface GetSystemSavedataRequest {
|
||||
clientSessionId: string;
|
||||
}
|
||||
|
||||
export class UpdateSystemSavedataRequest {
|
||||
export interface UpdateSystemSavedataRequest {
|
||||
clientSessionId: string;
|
||||
trainerId?: number;
|
||||
secretId?: number;
|
@ -8,20 +8,14 @@ import type { Variant } from "#sprites/variant";
|
||||
* 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 name of pokemon featured in the illusion */
|
||||
name: string;
|
||||
/** The nickname of the pokemon featured in the illusion */
|
||||
nickname?: string;
|
||||
/** Whether the pokemon featured in the illusion is shiny or not */
|
||||
shiny: boolean;
|
||||
/** The variant of the pokemon featured in the illusion */
|
||||
variant: Variant;
|
||||
/** The species of the illusion */
|
||||
species: SpeciesId;
|
||||
/** The formIndex of the illusion */
|
||||
@ -34,6 +28,10 @@ export interface IllusionData {
|
||||
fusionSpecies?: PokemonSpecies;
|
||||
/** The fusionFormIndex of the illusion */
|
||||
fusionFormIndex?: number;
|
||||
/** Whether the fusion species of the pokemon featured in the illusion is shiny or not */
|
||||
fusionShiny?: boolean;
|
||||
/** The variant of the fusion species of the pokemon featured in the illusion */
|
||||
fusionVariant?: Variant;
|
||||
/** The fusionGender of the illusion if it's a fusion */
|
||||
fusionGender?: Gender;
|
||||
/** The level of the illusion (not used currently) */
|
||||
|
@ -75,3 +75,14 @@ export type NonFunctionPropertiesRecursive<Class> = {
|
||||
};
|
||||
|
||||
export type AbstractConstructor<T> = abstract new (...args: any[]) => T;
|
||||
|
||||
/**
|
||||
* Type helper that iterates through the fields of the type and coerces any `null` properties to `undefined` (including in union types).
|
||||
*
|
||||
* @remarks
|
||||
* This is primarily useful when an object with nullable properties wants to be serialized and have its `null`
|
||||
* properties coerced to `undefined`.
|
||||
*/
|
||||
export type CoerceNullPropertiesToUndefined<T extends object> = {
|
||||
[K in keyof T]: null extends T[K] ? Exclude<T[K], null> | undefined : T[K];
|
||||
};
|
||||
|
10
src/@types/ui.ts
Normal file
10
src/@types/ui.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type Phaser from "phaser";
|
||||
import type InputText from "phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputText";
|
||||
|
||||
export interface TextStyleOptions {
|
||||
scale: number;
|
||||
styleOptions: Phaser.Types.GameObjects.Text.TextStyle | InputText.IConfig;
|
||||
shadowColor: string;
|
||||
shadowXpos: number;
|
||||
shadowYpos: number;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { pokerogueApi } from "#api/pokerogue-api";
|
||||
import { bypassLogin } from "#app/global-vars/bypass-login";
|
||||
import type { UserInfo } from "#types/UserInfo";
|
||||
import type { UserInfo } from "#types/user-info";
|
||||
import { randomString } from "#utils/common";
|
||||
|
||||
export let loggedInUser: UserInfo | null = null;
|
||||
|
@ -67,6 +67,7 @@ import { PokemonType } from "#enums/pokemon-type";
|
||||
import { ShopCursorTarget } from "#enums/shop-cursor-target";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
import type { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { TrainerVariant } from "#enums/trainer-variant";
|
||||
@ -132,7 +133,7 @@ import { CharSprite } from "#ui/char-sprite";
|
||||
import { PartyExpBar } from "#ui/party-exp-bar";
|
||||
import { PokeballTray } from "#ui/pokeball-tray";
|
||||
import { PokemonInfoContainer } from "#ui/pokemon-info-container";
|
||||
import { addTextObject, getTextColor, TextStyle } from "#ui/text";
|
||||
import { addTextObject, getTextColor } from "#ui/text";
|
||||
import { UI } from "#ui/ui";
|
||||
import { addUiThemeOverrides } from "#ui/ui-theme";
|
||||
import {
|
||||
@ -236,6 +237,7 @@ export class BattleScene extends SceneBase {
|
||||
public enableTouchControls = false;
|
||||
public enableVibration = false;
|
||||
public showBgmBar = true;
|
||||
public hideUsername = false;
|
||||
/** Determines the selected battle style. */
|
||||
public battleStyle: BattleStyle = BattleStyle.SWITCH;
|
||||
/**
|
||||
@ -699,16 +701,16 @@ export class BattleScene extends SceneBase {
|
||||
if (expSpriteKeys.size > 0) {
|
||||
return;
|
||||
}
|
||||
this.cachedFetch("./exp-sprites.json")
|
||||
.then(res => res.json())
|
||||
.then(keys => {
|
||||
if (Array.isArray(keys)) {
|
||||
for (const key of keys) {
|
||||
expSpriteKeys.add(key);
|
||||
}
|
||||
}
|
||||
Promise.resolve();
|
||||
});
|
||||
const res = await this.cachedFetch("./exp-sprites.json");
|
||||
const keys = await res.json();
|
||||
if (!Array.isArray(keys)) {
|
||||
throw new Error("EXP Sprites were not array when fetched!");
|
||||
}
|
||||
|
||||
// TODO: Optimize this
|
||||
for (const k of keys) {
|
||||
expSpriteKeys.add(k);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1669,6 +1671,11 @@ export class BattleScene extends SceneBase {
|
||||
case SpeciesId.MAUSHOLD:
|
||||
case SpeciesId.DUDUNSPARCE:
|
||||
return !randSeedInt(4) ? 1 : 0;
|
||||
case SpeciesId.SINISTEA:
|
||||
case SpeciesId.POLTEAGEIST:
|
||||
case SpeciesId.POLTCHAGEIST:
|
||||
case SpeciesId.SINISTCHA:
|
||||
return !randSeedInt(16) ? 1 : 0;
|
||||
case SpeciesId.PIKACHU:
|
||||
if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) {
|
||||
return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30
|
||||
|
@ -15,6 +15,7 @@ import { SpeciesFormChangeAbilityTrigger, SpeciesFormChangeWeatherTrigger } from
|
||||
import { Gender } from "#data/gender";
|
||||
import { getPokeballName } from "#data/pokeball";
|
||||
import { pokemonFormChanges } from "#data/pokemon-forms";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#data/status-effect";
|
||||
import { TerrainType } from "#data/terrain";
|
||||
import type { Weather } from "#data/weather";
|
||||
@ -28,12 +29,12 @@ import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import { Command } from "#enums/command";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { MoveFlags } from "#enums/MoveFlags";
|
||||
import { MoveTarget } from "#enums/MoveTarget";
|
||||
import { CommonAnim } from "#enums/move-anims-common";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveFlags } from "#enums/move-flags";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
import { MoveTarget } from "#enums/move-target";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { PokemonAnimType } from "#enums/pokemon-anim-type";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
@ -6001,8 +6002,13 @@ export class IllusionPreSummonAbAttr extends PreSummonAbAttr {
|
||||
const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(
|
||||
p => p.isAllowedInBattle(),
|
||||
);
|
||||
const lastPokemon: Pokemon = party.filter(p => p !== pokemon).at(-1) || pokemon;
|
||||
pokemon.setIllusion(lastPokemon);
|
||||
let illusionPokemon: Pokemon | PokemonSpecies;
|
||||
if (pokemon.hasTrainer()) {
|
||||
illusionPokemon = party.filter(p => p !== pokemon).at(-1) || pokemon;
|
||||
} else {
|
||||
illusionPokemon = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, pokemon.level);
|
||||
}
|
||||
pokemon.setIllusion(illusionPokemon);
|
||||
}
|
||||
|
||||
/** @returns Whether the illusion can be applied. */
|
||||
|
@ -9,10 +9,10 @@ import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { MoveTarget } from "#enums/MoveTarget";
|
||||
import { CommonAnim } from "#enums/move-anims-common";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveTarget } from "#enums/move-target";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { Stat } from "#enums/stat";
|
||||
|
@ -86,7 +86,7 @@ export enum BiomePoolTier {
|
||||
|
||||
export const uncatchableSpecies: SpeciesId[] = [];
|
||||
|
||||
export interface SpeciesTree {
|
||||
interface SpeciesTree {
|
||||
[key: number]: SpeciesId[]
|
||||
}
|
||||
|
||||
@ -94,11 +94,11 @@ export interface PokemonPools {
|
||||
[key: number]: (SpeciesId | SpeciesTree)[]
|
||||
}
|
||||
|
||||
export interface BiomeTierPokemonPools {
|
||||
interface BiomeTierPokemonPools {
|
||||
[key: number]: PokemonPools
|
||||
}
|
||||
|
||||
export interface BiomePokemonPools {
|
||||
interface BiomePokemonPools {
|
||||
[key: number]: BiomeTierPokemonPools
|
||||
}
|
||||
|
||||
@ -2022,7 +2022,6 @@ export const biomeTrainerPools: BiomeTrainerPools = {
|
||||
}
|
||||
};
|
||||
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: init methods are expected to have many lines.
|
||||
export function initBiomes() {
|
||||
const pokemonBiomes = [
|
||||
[ SpeciesId.BULBASAUR, PokemonType.GRASS, PokemonType.POISON, [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { allMoves } from "#data/data-lists";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { toReadableString } from "#utils/common";
|
||||
import { getEnumKeys, getEnumValues } from "#utils/enums";
|
||||
import { toTitleCase } from "#utils/strings";
|
||||
|
||||
export const speciesEggMoves = {
|
||||
[SpeciesId.BULBASAUR]: [ MoveId.SAPPY_SEED, MoveId.MALIGNANT_CHAIN, MoveId.EARTH_POWER, MoveId.MATCHA_GOTCHA ],
|
||||
@ -617,7 +617,7 @@ function parseEggMoves(content: string): void {
|
||||
}
|
||||
|
||||
if (eggMoves.every(m => m === MoveId.NONE)) {
|
||||
console.warn(`Species ${toReadableString(SpeciesId[species])} could not be parsed, excluding from output...`)
|
||||
console.warn(`Species ${toTitleCase(SpeciesId[species])} could not be parsed, excluding from output...`)
|
||||
} else {
|
||||
output += `[SpeciesId.${SpeciesId[species]}]: [ ${eggMoves.map(m => `MoveId.${MoveId[m]}`).join(", ")} ],\n`;
|
||||
}
|
||||
|
@ -3,12 +3,13 @@ import { allMoves } from "#data/data-lists";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { MoveFlags } from "#enums/MoveFlags";
|
||||
import { AnimBlendType, AnimFocus, AnimFrameTarget, ChargeAnim, CommonAnim } from "#enums/move-anims-common";
|
||||
import { MoveFlags } from "#enums/move-flags";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { animationFileName, coerceArray, getFrameMs, isNullOrUndefined, type nil } from "#utils/common";
|
||||
import { coerceArray, getFrameMs, isNullOrUndefined, type nil } from "#utils/common";
|
||||
import { getEnumKeys, getEnumValues } from "#utils/enums";
|
||||
import { toKebabCase } from "#utils/strings";
|
||||
import Phaser from "phaser";
|
||||
|
||||
export class AnimConfig {
|
||||
@ -412,7 +413,7 @@ export function initCommonAnims(): Promise<void> {
|
||||
const commonAnimId = commonAnimIds[ca];
|
||||
commonAnimFetches.push(
|
||||
globalScene
|
||||
.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/_/g, "-")}.json`)
|
||||
.cachedFetch(`./battle-anims/common-${toKebabCase(commonAnimNames[ca])}.json`)
|
||||
.then(response => response.json())
|
||||
.then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))),
|
||||
);
|
||||
@ -450,7 +451,7 @@ export function initMoveAnim(move: MoveId): Promise<void> {
|
||||
|
||||
const fetchAnimAndResolve = (move: MoveId) => {
|
||||
globalScene
|
||||
.cachedFetch(`./battle-anims/${animationFileName(move)}.json`)
|
||||
.cachedFetch(`./battle-anims/${toKebabCase(MoveId[move])}.json`)
|
||||
.then(response => {
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (!response.ok || contentType?.indexOf("application/json") === -1) {
|
||||
@ -506,7 +507,7 @@ function useDefaultAnim(move: MoveId, defaultMoveAnim: MoveId) {
|
||||
* @remarks use {@linkcode useDefaultAnim} to use a default animation
|
||||
*/
|
||||
function logMissingMoveAnim(move: MoveId, ...optionalParams: any[]) {
|
||||
const moveName = animationFileName(move);
|
||||
const moveName = toKebabCase(MoveId[move]);
|
||||
console.warn(`Could not load animation file for move '${moveName}'`, ...optionalParams);
|
||||
}
|
||||
|
||||
@ -524,7 +525,7 @@ export async function initEncounterAnims(encounterAnim: EncounterAnim | Encounte
|
||||
}
|
||||
encounterAnimFetches.push(
|
||||
globalScene
|
||||
.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/_/g, "-")}.json`)
|
||||
.cachedFetch(`./battle-anims/encounter-${toKebabCase(encounterAnimNames[anim])}.json`)
|
||||
.then(response => response.json())
|
||||
.then(cas => encounterAnims.set(anim, new AnimConfig(cas))),
|
||||
);
|
||||
@ -548,7 +549,7 @@ export function initMoveChargeAnim(chargeAnim: ChargeAnim): Promise<void> {
|
||||
} else {
|
||||
chargeAnims.set(chargeAnim, null);
|
||||
globalScene
|
||||
.cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/_/g, "-")}.json`)
|
||||
.cachedFetch(`./battle-anims/${toKebabCase(ChargeAnim[chargeAnim])}.json`)
|
||||
.then(response => response.json())
|
||||
.then(ca => {
|
||||
if (Array.isArray(ca)) {
|
||||
@ -1405,7 +1406,9 @@ export async function populateAnims() {
|
||||
const chargeAnimIds = getEnumValues(ChargeAnim);
|
||||
const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/;
|
||||
const moveNameToId = {};
|
||||
// Exclude MoveId.NONE;
|
||||
for (const move of getEnumValues(MoveId).slice(1)) {
|
||||
// KARATE_CHOP => KARATECHOP
|
||||
const moveName = MoveId[move].toUpperCase().replace(/_/g, "");
|
||||
moveNameToId[moveName] = move;
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ import { AbilityId } from "#enums/ability-id";
|
||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { MoveFlags } from "#enums/MoveFlags";
|
||||
import { ChargeAnim, CommonAnim } from "#enums/move-anims-common";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveFlags } from "#enums/move-flags";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
@ -4,6 +4,7 @@ import { defaultStarterSpecies } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { pokemonEvolutions } from "#balance/pokemon-evolutions";
|
||||
import { speciesStarterCosts } from "#balance/starters";
|
||||
import { getEggTierForSpecies } from "#data/egg";
|
||||
import { pokemonFormChanges } from "#data/pokemon-forms";
|
||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { getPokemonSpeciesForm } from "#data/pokemon-species";
|
||||
@ -11,6 +12,7 @@ import { BattleType } from "#enums/battle-type";
|
||||
import { ChallengeType } from "#enums/challenge-type";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { TypeColor, TypeShadow } from "#enums/color";
|
||||
import { EggTier } from "#enums/egg-type";
|
||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
@ -27,6 +29,7 @@ import type { DexAttrProps, GameData } from "#system/game-data";
|
||||
import { BooleanHolder, type NumberHolder, randSeedItem } from "#utils/common";
|
||||
import { deepCopy } from "#utils/data";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import { toCamelCase, toSnakeCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
/** A constant for the default max cost of the starting party before a run */
|
||||
@ -67,14 +70,11 @@ export abstract class Challenge {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the localisation key for the challenge
|
||||
* @returns {@link string} The i18n key for this challenge
|
||||
* Gets the localization key for the challenge
|
||||
* @returns The i18n key for this challenge as camel case.
|
||||
*/
|
||||
geti18nKey(): string {
|
||||
return Challenges[this.id]
|
||||
.split("_")
|
||||
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
|
||||
.join("");
|
||||
return toCamelCase(Challenges[this.id]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,23 +105,22 @@ export abstract class Challenge {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the textual representation of a challenge's current value.
|
||||
* @param overrideValue {@link number} The value to check for. If undefined, gets the current value.
|
||||
* @returns {@link string} The localised name for the current value.
|
||||
* Return the textual representation of a challenge's current value.
|
||||
* @param overrideValue - The value to check for; default {@linkcode this.value}
|
||||
* @returns The localised text for the current value.
|
||||
*/
|
||||
getValue(overrideValue?: number): string {
|
||||
const value = overrideValue ?? this.value;
|
||||
return i18next.t(`challenges:${this.geti18nKey()}.value.${value}`);
|
||||
getValue(overrideValue: number = this.value): string {
|
||||
return i18next.t(`challenges:${this.geti18nKey()}.value.${overrideValue}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of a challenge's current value.
|
||||
* @param overrideValue {@link number} The value to check for. If undefined, gets the current value.
|
||||
* @returns {@link string} The localised description for the current value.
|
||||
* Return the description of a challenge's current value.
|
||||
* @param overrideValue - The value to check for; default {@linkcode this.value}
|
||||
* @returns The localised description for the current value.
|
||||
*/
|
||||
getDescription(overrideValue?: number): string {
|
||||
const value = overrideValue ?? this.value;
|
||||
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${value}`, `challenges:${this.geti18nKey()}.desc`])}`;
|
||||
// TODO: Do we need an override value here? it's currently unused
|
||||
getDescription(overrideValue: number = this.value): string {
|
||||
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${overrideValue}`, `challenges:${this.geti18nKey()}.desc`])}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -579,31 +578,19 @@ export class SingleGenerationChallenge extends Challenge {
|
||||
return this.value > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the textual representation of a challenge's current value.
|
||||
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
|
||||
* @returns {string} The localised name for the current value.
|
||||
*/
|
||||
getValue(overrideValue?: number): string {
|
||||
const value = overrideValue ?? this.value;
|
||||
if (value === 0) {
|
||||
getValue(overrideValue: number = this.value): string {
|
||||
if (overrideValue === 0) {
|
||||
return i18next.t("settings:off");
|
||||
}
|
||||
return i18next.t(`starterSelectUiHandler:gen${value}`);
|
||||
return i18next.t(`starterSelectUiHandler:gen${overrideValue}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of a challenge's current value.
|
||||
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
|
||||
* @returns {string} The localised description for the current value.
|
||||
*/
|
||||
getDescription(overrideValue?: number): string {
|
||||
const value = overrideValue ?? this.value;
|
||||
if (value === 0) {
|
||||
getDescription(overrideValue: number = this.value): string {
|
||||
if (overrideValue === 0) {
|
||||
return i18next.t("challenges:singleGeneration.desc_default");
|
||||
}
|
||||
return i18next.t("challenges:singleGeneration.desc", {
|
||||
gen: i18next.t(`challenges:singleGeneration.gen_${value}`),
|
||||
gen: i18next.t(`challenges:singleGeneration.gen_${overrideValue}`),
|
||||
});
|
||||
}
|
||||
|
||||
@ -671,29 +658,13 @@ export class SingleTypeChallenge extends Challenge {
|
||||
return this.value > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the textual representation of a challenge's current value.
|
||||
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
|
||||
* @returns {string} The localised name for the current value.
|
||||
*/
|
||||
getValue(overrideValue?: number): string {
|
||||
if (overrideValue === undefined) {
|
||||
overrideValue = this.value;
|
||||
}
|
||||
return PokemonType[this.value - 1].toLowerCase();
|
||||
getValue(overrideValue: number = this.value): string {
|
||||
return toSnakeCase(PokemonType[overrideValue - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of a challenge's current value.
|
||||
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
|
||||
* @returns {string} The localised description for the current value.
|
||||
*/
|
||||
getDescription(overrideValue?: number): string {
|
||||
if (overrideValue === undefined) {
|
||||
overrideValue = this.value;
|
||||
}
|
||||
const type = i18next.t(`pokemonInfo:Type.${PokemonType[this.value - 1]}`);
|
||||
const typeColor = `[color=${TypeColor[PokemonType[this.value - 1]]}][shadow=${TypeShadow[PokemonType[this.value - 1]]}]${type}[/shadow][/color]`;
|
||||
getDescription(overrideValue: number = this.value): string {
|
||||
const type = i18next.t(`pokemonInfo:Type.${PokemonType[overrideValue - 1]}`);
|
||||
const typeColor = `[color=${TypeColor[PokemonType[overrideValue - 1]]}][shadow=${TypeShadow[PokemonType[this.value - 1]]}]${type}[/shadow][/color]`;
|
||||
const defaultDesc = i18next.t("challenges:singleType.desc_default");
|
||||
const typeDesc = i18next.t("challenges:singleType.desc", {
|
||||
type: typeColor,
|
||||
@ -714,11 +685,14 @@ export class SingleTypeChallenge extends Challenge {
|
||||
*/
|
||||
export class FreshStartChallenge extends Challenge {
|
||||
constructor() {
|
||||
super(Challenges.FRESH_START, 1);
|
||||
super(Challenges.FRESH_START, 3);
|
||||
}
|
||||
|
||||
applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder): boolean {
|
||||
if (!defaultStarterSpecies.includes(pokemon.speciesId)) {
|
||||
if (
|
||||
(this.value === 1 && !defaultStarterSpecies.includes(pokemon.speciesId)) ||
|
||||
(this.value === 2 && getEggTierForSpecies(pokemon) >= EggTier.EPIC)
|
||||
) {
|
||||
valid.value = false;
|
||||
return true;
|
||||
}
|
||||
@ -726,15 +700,12 @@ export class FreshStartChallenge extends Challenge {
|
||||
}
|
||||
|
||||
applyStarterCost(species: SpeciesId, cost: NumberHolder): boolean {
|
||||
if (defaultStarterSpecies.includes(species)) {
|
||||
cost.value = speciesStarterCosts[species];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
cost.value = speciesStarterCosts[species];
|
||||
return true;
|
||||
}
|
||||
|
||||
applyStarterModify(pokemon: Pokemon): boolean {
|
||||
pokemon.abilityIndex = 0; // Always base ability, not hidden ability
|
||||
pokemon.abilityIndex = pokemon.abilityIndex % 2; // Always base ability, if you set it to hidden it wraps to first ability
|
||||
pokemon.passive = false; // Passive isn't unlocked
|
||||
pokemon.nature = Nature.HARDY; // Neutral nature
|
||||
pokemon.moveset = pokemon.species
|
||||
@ -746,7 +717,22 @@ export class FreshStartChallenge extends Challenge {
|
||||
pokemon.luck = 0; // No luck
|
||||
pokemon.shiny = false; // Not shiny
|
||||
pokemon.variant = 0; // Not shiny
|
||||
pokemon.formIndex = 0; // Froakie should be base form
|
||||
if (pokemon.species.speciesId === SpeciesId.ZYGARDE && pokemon.formIndex >= 2) {
|
||||
pokemon.formIndex -= 2; // Sets 10%-PC to 10%-AB and 50%-PC to 50%-AB
|
||||
} else if (
|
||||
pokemon.formIndex > 0 &&
|
||||
[
|
||||
SpeciesId.PIKACHU,
|
||||
SpeciesId.EEVEE,
|
||||
SpeciesId.PICHU,
|
||||
SpeciesId.ROTOM,
|
||||
SpeciesId.MELOETTA,
|
||||
SpeciesId.FROAKIE,
|
||||
SpeciesId.ROCKRUFF,
|
||||
].includes(pokemon.species.speciesId)
|
||||
) {
|
||||
pokemon.formIndex = 0; // These mons are set to form 0 because they're meant to be unlocks or mid-run form changes
|
||||
}
|
||||
pokemon.ivs = [15, 15, 15, 15, 15, 15]; // Default IVs of 15 for all stats (Updated to 15 from 10 in 1.2.0)
|
||||
pokemon.teraType = pokemon.species.type1; // Always primary tera type
|
||||
return true;
|
||||
@ -832,13 +818,7 @@ export class LowerStarterMaxCostChallenge extends Challenge {
|
||||
super(Challenges.LOWER_MAX_STARTER_COST, 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getValue(overrideValue?: number): string {
|
||||
if (overrideValue === undefined) {
|
||||
overrideValue = this.value;
|
||||
}
|
||||
getValue(overrideValue: number = this.value): string {
|
||||
return (DEFAULT_PARTY_MAX_COST - overrideValue).toString();
|
||||
}
|
||||
|
||||
@ -866,13 +846,7 @@ export class LowerStarterPointsChallenge extends Challenge {
|
||||
super(Challenges.LOWER_STARTER_POINTS, 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getValue(overrideValue?: number): string {
|
||||
if (overrideValue === undefined) {
|
||||
overrideValue = this.value;
|
||||
}
|
||||
getValue(overrideValue: number = this.value): string {
|
||||
return (DEFAULT_PARTY_MAX_COST - overrideValue).toString();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { BattleSpec } from "#enums/battle-spec";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { trainerConfigs } from "#trainers/trainer-config";
|
||||
import { capitalizeFirstLetter } from "#utils/strings";
|
||||
|
||||
export interface TrainerTypeMessages {
|
||||
encounter?: string | string[];
|
||||
@ -1755,8 +1756,7 @@ export function initTrainerTypeDialogue(): void {
|
||||
trainerConfigs[trainerType][`${messageType}Messages`] = messages[0][messageType];
|
||||
}
|
||||
if (messages.length > 1) {
|
||||
trainerConfigs[trainerType][`female${messageType.slice(0, 1).toUpperCase()}${messageType.slice(1)}Messages`] =
|
||||
messages[1][messageType];
|
||||
trainerConfigs[trainerType][`female${capitalizeFirstLetter(messageType)}Messages`] = messages[1][messageType];
|
||||
}
|
||||
} else {
|
||||
trainerConfigs[trainerType][`${messageType}Messages`] = messages[messageType];
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { allMoves } from "#data/data-lists";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { MoveTarget } from "#enums/MoveTarget";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import { MoveTarget } from "#enums/move-target";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { applyMoveAttrs } from "#moves/apply-attrs";
|
||||
@ -27,6 +27,28 @@ export function isFieldTargeted(move: Move): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a move is a spread move.
|
||||
*
|
||||
* @param move - The {@linkcode Move} to check
|
||||
* @returns Whether {@linkcode move} is spread-targeted.
|
||||
* @remarks
|
||||
* Examples include:
|
||||
* - Moves targeting all adjacent Pokemon (like Surf)
|
||||
* - Moves targeting all adjacent enemies (like Air Cutter)
|
||||
*/
|
||||
|
||||
export function isSpreadMove(move: Move): boolean {
|
||||
switch (move.moveTarget) {
|
||||
case MoveTarget.ALL_ENEMIES:
|
||||
case MoveTarget.ALL_NEAR_ENEMIES:
|
||||
case MoveTarget.ALL_OTHERS:
|
||||
case MoveTarget.ALL_NEAR_OTHERS:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: MoveTarget): MoveTargetSet {
|
||||
const variableTarget = new NumberHolder(0);
|
||||
user.getOpponents(false).forEach(p => applyMoveAttrs("VariableTargetAttr", user, p, allMoves[move], variableTarget));
|
||||
|
@ -48,11 +48,11 @@ import { ChargeAnim } from "#enums/move-anims-common";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
import { isVirtual, MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
|
||||
import { MoveFlags } from "#enums/MoveFlags";
|
||||
import { MoveTarget } from "#enums/MoveTarget";
|
||||
import { MultiHitType } from "#enums/MultiHitType";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveEffectTrigger } from "#enums/move-effect-trigger";
|
||||
import { MoveFlags } from "#enums/move-flags";
|
||||
import { MoveTarget } from "#enums/move-target";
|
||||
import { MultiHitType } from "#enums/multi-hit-type";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import {
|
||||
@ -87,8 +87,9 @@ import type { AttackMoveResult } from "#types/attack-move-result";
|
||||
import type { Localizable } from "#types/locales";
|
||||
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString } from "#types/move-types";
|
||||
import type { TurnMove } from "#types/turn-move";
|
||||
import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue, toReadableString } from "#utils/common";
|
||||
import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
import { toTitleCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
/**
|
||||
@ -808,16 +809,14 @@ export abstract class Move implements Localizable {
|
||||
}
|
||||
|
||||
const power = new NumberHolder(this.power);
|
||||
|
||||
applyMoveAttrs("VariablePowerAttr", source, target, this, power);
|
||||
|
||||
const typeChangeMovePowerMultiplier = new NumberHolder(1);
|
||||
const typeChangeHolder = new NumberHolder(this.type);
|
||||
|
||||
applyAbAttrs("MoveTypeChangeAbAttr", {pokemon: source, opponent: target, move: this, simulated: true, moveType: typeChangeHolder, power: typeChangeMovePowerMultiplier});
|
||||
|
||||
const sourceTeraType = source.getTeraType();
|
||||
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
|
||||
power.value = 60;
|
||||
}
|
||||
|
||||
const abAttrParams: PreAttackModifyPowerAbAttrParams = {
|
||||
pokemon: source,
|
||||
opponent: target,
|
||||
@ -832,6 +831,13 @@ export abstract class Move implements Localizable {
|
||||
applyAbAttrs("AllyMoveCategoryPowerBoostAbAttr", {...abAttrParams, pokemon: ally});
|
||||
}
|
||||
|
||||
// Non-priority, single-hit moves of the user's Tera Type are always a bare minimum of 60 power
|
||||
|
||||
const sourceTeraType = source.getTeraType();
|
||||
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
|
||||
power.value = 60;
|
||||
}
|
||||
|
||||
const fieldAuras = new Set(
|
||||
globalScene.getField(true)
|
||||
.map((p) => p.getAbilityAttrs("FieldMoveTypePowerBoostAbAttr").filter(attr => {
|
||||
@ -855,7 +861,6 @@ export abstract class Move implements Localizable {
|
||||
power.value *= typeBoost.boostValue;
|
||||
}
|
||||
|
||||
applyMoveAttrs("VariablePowerAttr", source, target, this, power);
|
||||
|
||||
if (!this.hasAttr("TypelessAttr")) {
|
||||
globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power);
|
||||
@ -8133,7 +8138,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr {
|
||||
}
|
||||
const type = validTypes[user.randBattleSeedInt(validTypes.length)];
|
||||
user.summonData.types = [ type ];
|
||||
globalScene.phaseManager.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: toReadableString(PokemonType[type]) }));
|
||||
globalScene.phaseManager.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: toTitleCase(PokemonType[type]) }));
|
||||
user.updateInfo();
|
||||
|
||||
return true;
|
||||
|
@ -8,9 +8,9 @@ import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#data/data-lists";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { TextStyle } from "#ui/text";
|
||||
import type { TextStyle } from "#enums/text-style";
|
||||
|
||||
export class TextDisplay {
|
||||
speaker?: string;
|
||||
|
@ -25,7 +25,8 @@ import {
|
||||
StatusEffectRequirement,
|
||||
WaveRangeRequirement,
|
||||
} from "#mystery-encounters/mystery-encounter-requirements";
|
||||
import { capitalizeFirstLetter, coerceArray, isNullOrUndefined, randSeedInt } from "#utils/common";
|
||||
import { coerceArray, isNullOrUndefined, randSeedInt } from "#utils/common";
|
||||
import { capitalizeFirstLetter } from "#utils/strings";
|
||||
|
||||
export interface EncounterStartOfBattleEffect {
|
||||
sourcePokemon?: Pokemon;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { TextStyle } from "#enums/text-style";
|
||||
import { UiTheme } from "#enums/ui-theme";
|
||||
import type { TextStyle } from "#ui/text";
|
||||
import { getTextWithColors } from "#ui/text";
|
||||
import { isNullOrUndefined } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Nature } from "#enums/nature";
|
||||
import { EFFECTIVE_STATS, getShortenedStatKey, Stat } from "#enums/stat";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
import { UiTheme } from "#enums/ui-theme";
|
||||
import { getBBCodeFrag, TextStyle } from "#ui/text";
|
||||
import { toReadableString } from "#utils/common";
|
||||
import { getBBCodeFrag } from "#ui/text";
|
||||
import { toTitleCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
export function getNatureName(
|
||||
@ -12,7 +13,7 @@ export function getNatureName(
|
||||
ignoreBBCode = false,
|
||||
uiTheme: UiTheme = UiTheme.DEFAULT,
|
||||
): string {
|
||||
let ret = toReadableString(Nature[nature]);
|
||||
let ret = toTitleCase(Nature[nature]);
|
||||
//Translating nature
|
||||
if (i18next.exists(`nature:${ret}`)) {
|
||||
ret = i18next.t(`nature:${ret}` as any);
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
} from "#data/form-change-triggers";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { FormChangeItem } from "#enums/form-change-item";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
|
@ -29,15 +29,9 @@ import type { Variant, VariantSet } from "#sprites/variant";
|
||||
import { populateVariantColorCache, variantColorCache, variantData } from "#sprites/variant";
|
||||
import type { StarterMoveset } from "#system/game-data";
|
||||
import type { Localizable } from "#types/locales";
|
||||
import {
|
||||
capitalizeString,
|
||||
isNullOrUndefined,
|
||||
randSeedFloat,
|
||||
randSeedGauss,
|
||||
randSeedInt,
|
||||
randSeedItem,
|
||||
} from "#utils/common";
|
||||
import { isNullOrUndefined, randSeedFloat, randSeedGauss, randSeedInt, randSeedItem } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import { toCamelCase, toPascalCase } from "#utils/strings";
|
||||
import { argbFromRgba, QuantizerCelebi, rgbaFromArgb } from "@material/material-color-utilities";
|
||||
import i18next from "i18next";
|
||||
|
||||
@ -91,6 +85,7 @@ export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): Po
|
||||
return retSpecies;
|
||||
}
|
||||
|
||||
// TODO: Clean this up and seriously review alternate means of fusion naming
|
||||
export function getFusedSpeciesName(speciesAName: string, speciesBName: string): string {
|
||||
const fragAPattern = /([a-z]{2}.*?[aeiou(?:y$)\-']+)(.*?)$/i;
|
||||
const fragBPattern = /([a-z]{2}.*?[aeiou(?:y$)\-'])(.*?)$/i;
|
||||
@ -904,14 +899,14 @@ export class PokemonSpecies extends PokemonSpeciesForm implements Localizable {
|
||||
* @returns the pokemon-form locale key for the single form name ("Alolan Form", "Eternal Flower" etc)
|
||||
*/
|
||||
getFormNameToDisplay(formIndex = 0, append = false): string {
|
||||
const formKey = this.forms?.[formIndex!]?.formKey;
|
||||
const formText = capitalizeString(formKey, "-", false, false) || "";
|
||||
const speciesName = capitalizeString(SpeciesId[this.speciesId], "_", true, false);
|
||||
const formKey = this.forms[formIndex]?.formKey ?? "";
|
||||
const formText = toPascalCase(formKey);
|
||||
const speciesName = toCamelCase(SpeciesId[this.speciesId]);
|
||||
let ret = "";
|
||||
|
||||
const region = this.getRegion();
|
||||
if (this.speciesId === SpeciesId.ARCEUS) {
|
||||
ret = i18next.t(`pokemonInfo:Type.${formText?.toUpperCase()}`);
|
||||
ret = i18next.t(`pokemonInfo:Type.${formText.toUpperCase()}`);
|
||||
} else if (
|
||||
[
|
||||
SpeciesFormKey.MEGA,
|
||||
@ -937,7 +932,7 @@ export class PokemonSpecies extends PokemonSpeciesForm implements Localizable {
|
||||
if (i18next.exists(i18key)) {
|
||||
ret = i18next.t(i18key);
|
||||
} else {
|
||||
const rootSpeciesName = capitalizeString(SpeciesId[this.getRootSpeciesId()], "_", true, false);
|
||||
const rootSpeciesName = toCamelCase(SpeciesId[this.getRootSpeciesId()]);
|
||||
const i18RootKey = `pokemonForm:${rootSpeciesName}${formText}`;
|
||||
ret = i18next.exists(i18RootKey) ? i18next.t(i18RootKey) : formText;
|
||||
}
|
||||
@ -2851,11 +2846,11 @@ export function initSpecies() {
|
||||
new PokemonSpecies(SpeciesId.GRAPPLOCT, 8, false, false, false, "Jujitsu Pokémon", PokemonType.FIGHTING, null, 1.6, 39, AbilityId.LIMBER, AbilityId.NONE, AbilityId.TECHNICIAN, 480, 80, 118, 90, 70, 80, 42, 45, 50, 168, GrowthRate.MEDIUM_SLOW, 50, false),
|
||||
new PokemonSpecies(SpeciesId.SINISTEA, 8, false, false, false, "Black Tea Pokémon", PokemonType.GHOST, null, 0.1, 0.2, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, GrowthRate.MEDIUM_FAST, null, false, false,
|
||||
new PokemonForm("Phony Form", "phony", PokemonType.GHOST, null, 0.1, 0.2, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, "", true),
|
||||
new PokemonForm("Antique Form", "antique", PokemonType.GHOST, null, 0.1, 0.2, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, "", true, true),
|
||||
new PokemonForm("Antique Form", "antique", PokemonType.GHOST, null, 0.1, 0.2, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, "", true),
|
||||
),
|
||||
new PokemonSpecies(SpeciesId.POLTEAGEIST, 8, false, false, false, "Black Tea Pokémon", PokemonType.GHOST, null, 0.2, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 508, 60, 65, 65, 134, 114, 70, 60, 50, 178, GrowthRate.MEDIUM_FAST, null, false, false,
|
||||
new PokemonForm("Phony Form", "phony", PokemonType.GHOST, null, 0.2, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 508, 60, 65, 65, 134, 114, 70, 60, 50, 178, false, "", true),
|
||||
new PokemonForm("Antique Form", "antique", PokemonType.GHOST, null, 0.2, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 508, 60, 65, 65, 134, 114, 70, 60, 50, 178, false, "", true, true),
|
||||
new PokemonForm("Antique Form", "antique", PokemonType.GHOST, null, 0.2, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 508, 60, 65, 65, 134, 114, 70, 60, 50, 178, false, "", true),
|
||||
),
|
||||
new PokemonSpecies(SpeciesId.HATENNA, 8, false, false, false, "Calm Pokémon", PokemonType.PSYCHIC, null, 0.4, 3.4, AbilityId.HEALER, AbilityId.ANTICIPATION, AbilityId.MAGIC_BOUNCE, 265, 42, 30, 45, 56, 53, 39, 235, 50, 53, GrowthRate.SLOW, 0, false),
|
||||
new PokemonSpecies(SpeciesId.HATTREM, 8, false, false, false, "Serene Pokémon", PokemonType.PSYCHIC, null, 0.6, 4.8, AbilityId.HEALER, AbilityId.ANTICIPATION, AbilityId.MAGIC_BOUNCE, 370, 57, 40, 65, 86, 73, 49, 120, 50, 130, GrowthRate.SLOW, 0, false),
|
||||
@ -3109,11 +3104,11 @@ export function initSpecies() {
|
||||
new PokemonSpecies(SpeciesId.DIPPLIN, 9, false, false, false, "Candy Apple Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 4.4, AbilityId.SUPERSWEET_SYRUP, AbilityId.GLUTTONY, AbilityId.STICKY_HOLD, 485, 80, 80, 110, 95, 80, 40, 45, 50, 170, GrowthRate.ERRATIC, 50, false),
|
||||
new PokemonSpecies(SpeciesId.POLTCHAGEIST, 9, false, false, false, "Matcha Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, GrowthRate.SLOW, null, false, false,
|
||||
new PokemonForm("Counterfeit Form", "counterfeit", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, true),
|
||||
new PokemonForm("Artisan Form", "artisan", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, false, true),
|
||||
new PokemonForm("Artisan Form", "artisan", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, "counterfeit", true),
|
||||
),
|
||||
new PokemonSpecies(SpeciesId.SINISTCHA, 9, false, false, false, "Matcha Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.2, 2.2, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 508, 71, 60, 106, 121, 80, 70, 60, 50, 178, GrowthRate.SLOW, null, false, false,
|
||||
new PokemonForm("Unremarkable Form", "unremarkable", PokemonType.GRASS, PokemonType.GHOST, 0.2, 2.2, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 508, 71, 60, 106, 121, 80, 70, 60, 50, 178),
|
||||
new PokemonForm("Masterpiece Form", "masterpiece", PokemonType.GRASS, PokemonType.GHOST, 0.2, 2.2, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 508, 71, 60, 106, 121, 80, 70, 60, 50, 178, false, null, false, true),
|
||||
new PokemonForm("Unremarkable Form", "unremarkable", PokemonType.GRASS, PokemonType.GHOST, 0.2, 2.2, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 508, 71, 60, 106, 121, 80, 70, 60, 50, 178, false, null, true),
|
||||
new PokemonForm("Masterpiece Form", "masterpiece", PokemonType.GRASS, PokemonType.GHOST, 0.2, 2.2, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 508, 71, 60, 106, 121, 80, 70, 60, 50, 178, false, "unremarkable", true),
|
||||
),
|
||||
new PokemonSpecies(SpeciesId.OKIDOGI, 9, true, false, false, "Retainer Pokémon", PokemonType.POISON, PokemonType.FIGHTING, 1.8, 92.2, AbilityId.TOXIC_CHAIN, AbilityId.NONE, AbilityId.GUARD_DOG, 555, 88, 128, 115, 58, 86, 80, 3, 0, 276, GrowthRate.SLOW, 100, false),
|
||||
new PokemonSpecies(SpeciesId.MUNKIDORI, 9, true, false, false, "Retainer Pokémon", PokemonType.POISON, PokemonType.PSYCHIC, 1, 12.2, AbilityId.TOXIC_CHAIN, AbilityId.NONE, AbilityId.FRISK, 555, 88, 75, 66, 130, 90, 106, 3, 0, 276, GrowthRate.SLOW, 100, false),
|
||||
|
@ -1,18 +1,29 @@
|
||||
import { type BattlerTag, loadBattlerTag } from "#data/battler-tags";
|
||||
import { allSpecies } from "#data/data-lists";
|
||||
import type { Gender } from "#data/gender";
|
||||
import { PokemonMove } from "#data/moves/pokemon-move";
|
||||
import type { PokemonSpeciesForm } from "#data/pokemon-species";
|
||||
import { getPokemonSpeciesForm, type PokemonSpeciesForm } from "#data/pokemon-species";
|
||||
import type { TypeDamageMultiplier } from "#data/type";
|
||||
import type { AbilityId } from "#enums/ability-id";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { Nature } from "#enums/nature";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
import type { AttackMoveResult } from "#types/attack-move-result";
|
||||
import type { IllusionData } from "#types/illusion-data";
|
||||
import type { TurnMove } from "#types/turn-move";
|
||||
import type { CoerceNullPropertiesToUndefined } from "#types/type-helpers";
|
||||
import { isNullOrUndefined } from "#utils/common";
|
||||
|
||||
/**
|
||||
* The type that {@linkcode PokemonSpeciesForm} is converted to when an object containing it serializes it.
|
||||
*/
|
||||
type SerializedSpeciesForm = {
|
||||
id: SpeciesId;
|
||||
formIdx: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Permanent data that can customize a Pokemon in non-standard ways from its Species.
|
||||
* Includes abilities, nature, changed types, etc.
|
||||
@ -41,9 +52,59 @@ export class CustomPokemonData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a pokemon species form from an object containing `id` and `formIdx` properties.
|
||||
* @param value - The value to deserialize
|
||||
* @returns The `PokemonSpeciesForm` or `null` if the fields could not be properly discerned
|
||||
*/
|
||||
function deserializePokemonSpeciesForm(value: SerializedSpeciesForm | PokemonSpeciesForm): PokemonSpeciesForm | null {
|
||||
// @ts-expect-error: We may be deserializing a PokemonSpeciesForm, but we catch later on
|
||||
let { id, formIdx } = value;
|
||||
|
||||
if (isNullOrUndefined(id) || isNullOrUndefined(formIdx)) {
|
||||
// @ts-expect-error: Typescript doesn't know that in block, `value` must be a PokemonSpeciesForm
|
||||
id = value.speciesId;
|
||||
// @ts-expect-error: Same as above (plus we are accessing a protected property)
|
||||
formIdx = value._formIndex;
|
||||
}
|
||||
// If for some reason either of these fields are null/undefined, we cannot reconstruct the species form
|
||||
if (isNullOrUndefined(id) || isNullOrUndefined(formIdx)) {
|
||||
return null;
|
||||
}
|
||||
return getPokemonSpeciesForm(id, formIdx);
|
||||
}
|
||||
|
||||
interface SerializedIllusionData extends Omit<IllusionData, "fusionSpecies"> {
|
||||
/** The id of the illusioned fusion species, or `undefined` if not a fusion */
|
||||
fusionSpecies?: SpeciesId;
|
||||
}
|
||||
|
||||
interface SerializedPokemonSummonData {
|
||||
statStages: number[];
|
||||
moveQueue: TurnMove[];
|
||||
tags: BattlerTag[];
|
||||
abilitySuppressed: boolean;
|
||||
speciesForm?: SerializedSpeciesForm;
|
||||
fusionSpeciesForm?: SerializedSpeciesForm;
|
||||
ability?: AbilityId;
|
||||
passiveAbility?: AbilityId;
|
||||
gender?: Gender;
|
||||
fusionGender?: Gender;
|
||||
stats: number[];
|
||||
moveset?: PokemonMove[];
|
||||
types: PokemonType[];
|
||||
addedType?: PokemonType;
|
||||
illusion?: SerializedIllusionData;
|
||||
illusionBroken: boolean;
|
||||
berriesEatenLast: BerryType[];
|
||||
moveHistory: TurnMove[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistent in-battle data for a {@linkcode Pokemon}.
|
||||
* Resets on switch or new battle.
|
||||
*
|
||||
* @sealed
|
||||
*/
|
||||
export class PokemonSummonData {
|
||||
/** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */
|
||||
@ -86,7 +147,7 @@ export class PokemonSummonData {
|
||||
*/
|
||||
public moveHistory: TurnMove[] = [];
|
||||
|
||||
constructor(source?: PokemonSummonData | Partial<PokemonSummonData>) {
|
||||
constructor(source?: PokemonSummonData | SerializedPokemonSummonData) {
|
||||
if (isNullOrUndefined(source)) {
|
||||
return;
|
||||
}
|
||||
@ -97,6 +158,30 @@ export class PokemonSummonData {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === "speciesForm" || key === "fusionSpeciesForm") {
|
||||
this[key] = deserializePokemonSpeciesForm(value);
|
||||
}
|
||||
|
||||
if (key === "illusion" && typeof value === "object") {
|
||||
// Make a copy so as not to mutate provided value
|
||||
const illusionData = {
|
||||
...value,
|
||||
};
|
||||
if (!isNullOrUndefined(illusionData.fusionSpecies)) {
|
||||
switch (typeof illusionData.fusionSpecies) {
|
||||
case "object":
|
||||
illusionData.fusionSpecies = allSpecies[illusionData.fusionSpecies.speciesId];
|
||||
break;
|
||||
case "number":
|
||||
illusionData.fusionSpecies = allSpecies[illusionData.fusionSpecies];
|
||||
break;
|
||||
default:
|
||||
illusionData.fusionSpecies = undefined;
|
||||
}
|
||||
}
|
||||
this[key] = illusionData as IllusionData;
|
||||
}
|
||||
|
||||
if (key === "moveset") {
|
||||
this.moveset = value?.map((m: any) => PokemonMove.loadMove(m));
|
||||
continue;
|
||||
@ -110,6 +195,49 @@ export class PokemonSummonData {
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize this PokemonSummonData to JSON, converting {@linkcode PokemonSpeciesForm} and {@linkcode IllusionData.fusionSpecies}
|
||||
* into simpler types instead of serializing all of their fields.
|
||||
*
|
||||
* @remarks
|
||||
* - `IllusionData.fusionSpecies` is serialized as just the species ID
|
||||
* - `PokemonSpeciesForm` and `PokemonSpeciesForm.fusionSpeciesForm` are converted into {@linkcode SerializedSpeciesForm} objects
|
||||
*/
|
||||
public toJSON(): SerializedPokemonSummonData {
|
||||
// Pokemon species forms are never saved, only the species ID.
|
||||
const illusion = this.illusion;
|
||||
const speciesForm = this.speciesForm;
|
||||
const fusionSpeciesForm = this.fusionSpeciesForm;
|
||||
const illusionSpeciesForm = illusion?.fusionSpecies;
|
||||
const t = {
|
||||
// the "as omit" is required to avoid TS resolving the overwritten properties to "never"
|
||||
// We coerce null to undefined in the type, as the for loop below replaces `null` with `undefined`
|
||||
...(this as Omit<
|
||||
CoerceNullPropertiesToUndefined<PokemonSummonData>,
|
||||
"speciesForm" | "fusionSpeciesForm" | "illusion"
|
||||
>),
|
||||
speciesForm: isNullOrUndefined(speciesForm)
|
||||
? undefined
|
||||
: { id: speciesForm.speciesId, formIdx: speciesForm.formIndex },
|
||||
fusionSpeciesForm: isNullOrUndefined(fusionSpeciesForm)
|
||||
? undefined
|
||||
: { id: fusionSpeciesForm.speciesId, formIdx: fusionSpeciesForm.formIndex },
|
||||
illusion: isNullOrUndefined(illusion)
|
||||
? undefined
|
||||
: {
|
||||
...(this.illusion as Omit<typeof illusion, "fusionSpecies">),
|
||||
fusionSpecies: illusionSpeciesForm?.speciesId,
|
||||
},
|
||||
};
|
||||
// Replace `null` with `undefined`, as `undefined` never gets serialized
|
||||
for (const [key, value] of Object.entries(t)) {
|
||||
if (value === null) {
|
||||
t[key] = undefined;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added
|
||||
|
@ -3,6 +3,7 @@ import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { Move } from "#moves/move";
|
||||
import { isFieldTargeted, isSpreadMove } from "#moves/move-utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
export enum TerrainType {
|
||||
@ -60,13 +61,19 @@ export class Terrain {
|
||||
isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean {
|
||||
switch (this.terrainType) {
|
||||
case TerrainType.PSYCHIC:
|
||||
if (!move.hasAttr("ProtectAttr")) {
|
||||
// Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain
|
||||
return (
|
||||
move.getPriority(user) > 0 &&
|
||||
user.getOpponents(true).some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded())
|
||||
);
|
||||
}
|
||||
// Cf https://bulbapedia.bulbagarden.net/wiki/Psychic_Terrain_(move)#Generation_VII
|
||||
// Psychic terrain will only cancel a move if it:
|
||||
return (
|
||||
// ... is neither spread nor field-targeted,
|
||||
!isFieldTargeted(move) &&
|
||||
!isSpreadMove(move) &&
|
||||
// .. has positive final priority,
|
||||
move.getPriority(user) > 0 &&
|
||||
// ...and is targeting at least 1 grounded opponent
|
||||
user
|
||||
.getOpponents(true)
|
||||
.some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded())
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { toReadableString } from "#utils/common";
|
||||
import { toPascalSnakeCase } from "#utils/strings";
|
||||
|
||||
class TrainerNameConfig {
|
||||
public urls: string[];
|
||||
public femaleUrls: string[] | null;
|
||||
|
||||
constructor(type: TrainerType, ...urls: string[]) {
|
||||
this.urls = urls.length ? urls : [toReadableString(TrainerType[type]).replace(/ /g, "_")];
|
||||
this.urls = urls.length ? urls : [toPascalSnakeCase(TrainerType[type])];
|
||||
}
|
||||
|
||||
hasGenderVariant(...femaleUrls: string[]): TrainerNameConfig {
|
||||
|
@ -41,15 +41,9 @@ import type {
|
||||
TrainerConfigs,
|
||||
TrainerTierPools,
|
||||
} from "#types/trainer-funcs";
|
||||
import {
|
||||
coerceArray,
|
||||
isNullOrUndefined,
|
||||
randSeedInt,
|
||||
randSeedIntRange,
|
||||
randSeedItem,
|
||||
toReadableString,
|
||||
} from "#utils/common";
|
||||
import { coerceArray, isNullOrUndefined, randSeedInt, randSeedIntRange, randSeedItem } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import { toSnakeCase, toTitleCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
/** Minimum BST for Pokemon generated onto the Elite Four's teams */
|
||||
@ -140,7 +134,7 @@ export class TrainerConfig {
|
||||
constructor(trainerType: TrainerType, allowLegendaries?: boolean) {
|
||||
this.trainerType = trainerType;
|
||||
this.trainerAI = new TrainerAI();
|
||||
this.name = toReadableString(TrainerType[this.getDerivedType()]);
|
||||
this.name = toTitleCase(TrainerType[this.getDerivedType()]);
|
||||
this.battleBgm = "battle_trainer";
|
||||
this.mixedBattleBgm = "battle_trainer";
|
||||
this.victoryBgm = "victory_trainer";
|
||||
@ -734,7 +728,7 @@ export class TrainerConfig {
|
||||
}
|
||||
|
||||
// Localize the trainer's name by converting it to lowercase and replacing spaces with underscores.
|
||||
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
|
||||
const nameForCall = toSnakeCase(this.name);
|
||||
this.name = i18next.t(`trainerNames:${nameForCall}`);
|
||||
|
||||
// Set the title to "elite_four". (this is the key in the i18n file)
|
||||
|
59
src/enums/text-style.ts
Normal file
59
src/enums/text-style.ts
Normal file
@ -0,0 +1,59 @@
|
||||
export const TextStyle = Object.freeze({
|
||||
MESSAGE: 1,
|
||||
WINDOW: 2,
|
||||
WINDOW_ALT: 3,
|
||||
WINDOW_BATTLE_COMMAND: 4,
|
||||
BATTLE_INFO: 5,
|
||||
PARTY: 6,
|
||||
PARTY_RED: 7,
|
||||
PARTY_CANCEL_BUTTON: 8,
|
||||
INSTRUCTIONS_TEXT: 9,
|
||||
MOVE_LABEL: 10,
|
||||
SUMMARY: 11,
|
||||
SUMMARY_DEX_NUM: 12,
|
||||
SUMMARY_DEX_NUM_GOLD: 13,
|
||||
SUMMARY_ALT: 14,
|
||||
SUMMARY_HEADER: 15,
|
||||
SUMMARY_RED: 16,
|
||||
SUMMARY_BLUE: 17,
|
||||
SUMMARY_PINK: 18,
|
||||
SUMMARY_GOLD: 19,
|
||||
SUMMARY_GRAY: 20,
|
||||
SUMMARY_GREEN: 21,
|
||||
SUMMARY_STATS: 22,
|
||||
SUMMARY_STATS_BLUE: 23,
|
||||
SUMMARY_STATS_PINK: 24,
|
||||
SUMMARY_STATS_GOLD: 25,
|
||||
LUCK_VALUE: 26,
|
||||
STATS_HEXAGON: 27,
|
||||
GROWTH_RATE_TYPE: 28,
|
||||
MONEY: 29, // Money default styling (pale yellow)
|
||||
MONEY_WINDOW: 30, // Money displayed in Windows (needs different colors based on theme)
|
||||
HEADER_LABEL: 31,
|
||||
STATS_LABEL: 32,
|
||||
STATS_VALUE: 33,
|
||||
SETTINGS_VALUE: 34,
|
||||
SETTINGS_LABEL: 35,
|
||||
SETTINGS_LABEL_NAVBAR: 36,
|
||||
SETTINGS_SELECTED: 37,
|
||||
SETTINGS_LOCKED: 38,
|
||||
EGG_LIST: 39,
|
||||
EGG_SUMMARY_NAME: 40,
|
||||
EGG_SUMMARY_DEX: 41,
|
||||
STARTER_VALUE_LIMIT: 42,
|
||||
TOOLTIP_TITLE: 43,
|
||||
TOOLTIP_CONTENT: 44,
|
||||
FILTER_BAR_MAIN: 45,
|
||||
MOVE_INFO_CONTENT: 46,
|
||||
MOVE_PP_FULL: 47,
|
||||
MOVE_PP_HALF_FULL: 48,
|
||||
MOVE_PP_NEAR_EMPTY: 49,
|
||||
MOVE_PP_EMPTY: 50,
|
||||
SMALLER_WINDOW_ALT: 51,
|
||||
BGM_BAR: 52,
|
||||
PERFECT_IV: 53,
|
||||
ME_OPTION_DEFAULT: 54, // Default style for choices in ME
|
||||
ME_OPTION_SPECIAL: 55, // Style for choices with special requirements in ME
|
||||
SHADOW_TEXT: 56 // to obscure unavailable options
|
||||
})
|
||||
export type TextStyle = typeof TextStyle[keyof typeof TextStyle];
|
@ -1,9 +1,10 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { DamageResult } from "#types/damage-result";
|
||||
import { addTextObject, TextStyle } from "#ui/text";
|
||||
import { addTextObject } from "#ui/text";
|
||||
import { fixedInt, formatStat } from "#utils/common";
|
||||
|
||||
type TextAndShadowArr = [string | null, string | null];
|
||||
|
@ -5,10 +5,11 @@ import { coerceArray, fixedInt, randInt } from "#utils/common";
|
||||
export class PokemonSpriteSparkleHandler {
|
||||
private sprites: Set<Phaser.GameObjects.Sprite>;
|
||||
|
||||
private counterTween?: Phaser.Tweens.Tween;
|
||||
|
||||
setup(): void {
|
||||
this.sprites = new Set();
|
||||
|
||||
globalScene.tweens.addCounter({
|
||||
this.counterTween = globalScene.tweens.addCounter({
|
||||
duration: fixedInt(200),
|
||||
from: 0,
|
||||
to: 1,
|
||||
@ -78,4 +79,12 @@ export class PokemonSpriteSparkleHandler {
|
||||
this.sprites.delete(s);
|
||||
}
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.removeAll();
|
||||
if (this.counterTween) {
|
||||
this.counterTween.destroy();
|
||||
this.counterTween = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,11 +82,11 @@ import { DexAttr } from "#enums/dex-attr";
|
||||
import { FieldPosition } from "#enums/field-position";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { LearnMoveSituation } from "#enums/learn-move-situation";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { MoveFlags } from "#enums/MoveFlags";
|
||||
import { MoveTarget } from "#enums/MoveTarget";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveFlags } from "#enums/move-flags";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveTarget } from "#enums/move-target";
|
||||
import { isIgnorePP, isVirtual, MoveUseMode } from "#enums/move-use-mode";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
@ -213,8 +213,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* TODO: Stop treating this like a unique ID and stop treating 0 as no pokemon
|
||||
*/
|
||||
public id: number;
|
||||
public name: string;
|
||||
public nickname: string;
|
||||
/**
|
||||
* The Pokemon's current nickname, or `undefined` if it currently lacks one.
|
||||
* If omitted, references to this should refer to the default name for this Pokemon's species.
|
||||
*/
|
||||
public nickname?: string;
|
||||
public species: PokemonSpecies;
|
||||
public formIndex: number;
|
||||
public abilityIndex: number;
|
||||
@ -442,10 +445,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @returns The name to render for this {@linkcode Pokemon}.
|
||||
*/
|
||||
getNameToRender(useIllusion = true) {
|
||||
const name: string =
|
||||
!useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.name : this.name;
|
||||
const nickname: string =
|
||||
!useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.nickname : this.nickname;
|
||||
const illusion = this.summonData.illusion;
|
||||
const name = useIllusion ? (illusion?.name ?? this.name) : this.name;
|
||||
const nickname: string | undefined = useIllusion ? illusion?.nickname : this.nickname;
|
||||
try {
|
||||
if (nickname) {
|
||||
return decodeURIComponent(escape(atob(nickname))); // TODO: Remove `atob` and `escape`... eventually...
|
||||
@ -463,7 +465,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @returns The {@linkcode PokeballType} that will be shown when this Pokemon is sent out into battle.
|
||||
*/
|
||||
getPokeball(useIllusion = false): PokeballType {
|
||||
return useIllusion && this.summonData.illusion ? this.summonData.illusion.pokeball : this.pokeball;
|
||||
return useIllusion ? (this.summonData.illusion?.pokeball ?? this.pokeball) : this.pokeball;
|
||||
}
|
||||
|
||||
init(): void {
|
||||
@ -609,24 +611,33 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an illusion of the last pokemon in the party, as other wild pokemon in the area.
|
||||
* Set this pokemon's illusion to the data of the given pokemon.
|
||||
*
|
||||
* @remarks
|
||||
* When setting the illusion of a wild pokemon, a {@linkcode PokemonSpecies} is generally passed.
|
||||
* When setting the illusion of a pokemon in this way, the fields required by illusion data
|
||||
* but missing from `PokemonSpecies` are set as follows
|
||||
* - `pokeball` and `nickname` are both inherited from this pokemon
|
||||
* - `shiny` will always be set if this pokemon OR its fusion is shiny
|
||||
* - `variant` will always be 0
|
||||
* - Fields related to fusion will be set to `undefined` or `0` as appropriate
|
||||
* - The gender is set to be the same as this pokemon, if it is compatible with the provided pokemon.
|
||||
* - If the provided pokemon can only ever exist as one gender, it is always that gender
|
||||
* - If this pokemon is genderless but the provided pokemon isn't, then a gender roll is done based on this
|
||||
* pokemon's ID
|
||||
*/
|
||||
setIllusion(pokemon: Pokemon): boolean {
|
||||
if (this.summonData.illusion) {
|
||||
this.breakIllusion();
|
||||
}
|
||||
if (this.hasTrainer()) {
|
||||
setIllusion(pokemon: Pokemon | PokemonSpecies): boolean {
|
||||
this.breakIllusion();
|
||||
if (pokemon instanceof Pokemon) {
|
||||
const speciesId = pokemon.species.speciesId;
|
||||
|
||||
this.summonData.illusion = {
|
||||
basePokemon: {
|
||||
name: this.name,
|
||||
nickname: this.nickname,
|
||||
shiny: this.shiny,
|
||||
variant: this.variant,
|
||||
fusionShiny: this.fusionShiny,
|
||||
fusionVariant: this.fusionVariant,
|
||||
},
|
||||
name: pokemon.name,
|
||||
nickname: pokemon.nickname,
|
||||
shiny: pokemon.shiny,
|
||||
variant: pokemon.variant,
|
||||
fusionShiny: pokemon.fusionShiny,
|
||||
fusionVariant: pokemon.fusionVariant,
|
||||
species: speciesId,
|
||||
formIndex: pokemon.formIndex,
|
||||
gender: pokemon.gender,
|
||||
@ -636,54 +647,61 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
fusionGender: pokemon.fusionGender,
|
||||
};
|
||||
|
||||
this.name = pokemon.name;
|
||||
this.nickname = pokemon.nickname;
|
||||
this.shiny = pokemon.shiny;
|
||||
this.variant = pokemon.variant;
|
||||
this.fusionVariant = pokemon.fusionVariant;
|
||||
this.fusionShiny = pokemon.fusionShiny;
|
||||
if (this.shiny) {
|
||||
if (pokemon.shiny || pokemon.fusionShiny) {
|
||||
this.initShinySparkle();
|
||||
}
|
||||
this.loadAssets(false, true).then(() => this.playAnim());
|
||||
this.updateInfo();
|
||||
} else {
|
||||
const randomIllusion: PokemonSpecies = globalScene.arena.randomSpecies(
|
||||
globalScene.currentBattle.waveIndex,
|
||||
this.level,
|
||||
);
|
||||
|
||||
// Correct the gender in case the illusioned species has a gender incompatible with this pokemon
|
||||
let gender = this.gender;
|
||||
switch (pokemon.malePercent) {
|
||||
case null:
|
||||
gender = Gender.GENDERLESS;
|
||||
break;
|
||||
case 0:
|
||||
gender = Gender.FEMALE;
|
||||
break;
|
||||
case 100:
|
||||
gender = Gender.MALE;
|
||||
break;
|
||||
default:
|
||||
gender = (this.id % 256) * 0.390625 < pokemon.malePercent ? Gender.MALE : Gender.FEMALE;
|
||||
}
|
||||
/*
|
||||
TODO: Allow setting `variant` to something other than 0, which would require first loading the
|
||||
assets for the provided species, as its entry would otherwise not
|
||||
be guaranteed to exist in the `variantData` map. But this would prevent `summonData` from being populated
|
||||
until the assets are loaded, which would cause issues as this method cannot be easily promisified.
|
||||
*/
|
||||
this.summonData.illusion = {
|
||||
basePokemon: {
|
||||
name: this.name,
|
||||
nickname: this.nickname,
|
||||
shiny: this.shiny,
|
||||
variant: this.variant,
|
||||
fusionShiny: this.fusionShiny,
|
||||
fusionVariant: this.fusionVariant,
|
||||
},
|
||||
species: randomIllusion.speciesId,
|
||||
formIndex: randomIllusion.formIndex,
|
||||
gender: this.gender,
|
||||
fusionShiny: false,
|
||||
fusionVariant: 0,
|
||||
shiny: this.shiny || this.fusionShiny,
|
||||
variant: 0,
|
||||
nickname: this.nickname,
|
||||
name: pokemon.name,
|
||||
species: pokemon.speciesId,
|
||||
formIndex: pokemon.formIndex,
|
||||
gender,
|
||||
pokeball: this.pokeball,
|
||||
};
|
||||
|
||||
this.name = randomIllusion.name;
|
||||
this.loadAssets(false, true).then(() => this.playAnim());
|
||||
if (this.shiny || this.fusionShiny) {
|
||||
this.initShinySparkle();
|
||||
}
|
||||
}
|
||||
this.loadAssets(false, true).then(() => this.playAnim());
|
||||
this.updateInfo();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Break the illusion of this pokemon, if it has an active illusion.
|
||||
* @returns Whether an illusion was broken.
|
||||
*/
|
||||
breakIllusion(): boolean {
|
||||
if (!this.summonData.illusion) {
|
||||
return false;
|
||||
}
|
||||
this.name = this.summonData.illusion.basePokemon.name;
|
||||
this.nickname = this.summonData.illusion.basePokemon.nickname;
|
||||
this.shiny = this.summonData.illusion.basePokemon.shiny;
|
||||
this.variant = this.summonData.illusion.basePokemon.variant;
|
||||
this.fusionVariant = this.summonData.illusion.basePokemon.fusionVariant;
|
||||
this.fusionShiny = this.summonData.illusion.basePokemon.fusionShiny;
|
||||
this.summonData.illusion = null;
|
||||
if (this.isOnField()) {
|
||||
globalScene.playSound("PRSFX- Transform");
|
||||
@ -718,8 +736,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
// Assets for moves
|
||||
loadPromises.push(loadMoveAnimations(this.getMoveset().map(m => m.getMove().id)));
|
||||
|
||||
/** alias for `this.summonData.illusion`; bangs on this are safe when guarded with `useIllusion` being true */
|
||||
const illusion = this.summonData.illusion;
|
||||
useIllusion = useIllusion && !!illusion;
|
||||
|
||||
// Load the assets for the species form
|
||||
const formIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.formIndex : this.formIndex;
|
||||
const formIndex = useIllusion ? illusion!.formIndex : this.formIndex;
|
||||
loadPromises.push(
|
||||
this.getSpeciesForm(false, useIllusion).loadAssets(
|
||||
this.getGender(useIllusion) === Gender.FEMALE,
|
||||
@ -736,16 +758,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
);
|
||||
}
|
||||
if (this.getFusionSpeciesForm()) {
|
||||
const fusionFormIndex =
|
||||
useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex : this.fusionFormIndex;
|
||||
const fusionShiny =
|
||||
!useIllusion && this.summonData.illusion?.basePokemon
|
||||
? this.summonData.illusion.basePokemon.fusionShiny
|
||||
: this.fusionShiny;
|
||||
const fusionVariant =
|
||||
!useIllusion && this.summonData.illusion?.basePokemon
|
||||
? this.summonData.illusion.basePokemon.fusionVariant
|
||||
: this.fusionVariant;
|
||||
const { fusionFormIndex, fusionShiny, fusionVariant } = useIllusion ? illusion! : this;
|
||||
loadPromises.push(
|
||||
this.getFusionSpeciesForm(false, useIllusion).loadAssets(
|
||||
this.getFusionGender(false, useIllusion) === Gender.FEMALE,
|
||||
@ -933,8 +946,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return this.getSpeciesForm(ignoreOverride, false).getSpriteKey(
|
||||
this.getGender(ignoreOverride) === Gender.FEMALE,
|
||||
this.formIndex,
|
||||
this.summonData.illusion?.basePokemon.shiny ?? this.shiny,
|
||||
this.summonData.illusion?.basePokemon.variant ?? this.variant,
|
||||
this.isShiny(false),
|
||||
this.getVariant(false),
|
||||
);
|
||||
}
|
||||
|
||||
@ -977,11 +990,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
getIconAtlasKey(ignoreOverride = false, useIllusion = true): string {
|
||||
// TODO: confirm the correct behavior here (is it intentional that the check fails if `illusion.formIndex` is `0`?)
|
||||
const formIndex =
|
||||
useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion.formIndex : this.formIndex;
|
||||
const variant =
|
||||
!useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.variant : this.variant;
|
||||
const illusion = this.summonData.illusion;
|
||||
const { formIndex, variant } = useIllusion && illusion ? illusion : this;
|
||||
return this.getSpeciesForm(ignoreOverride, useIllusion).getIconAtlasKey(
|
||||
formIndex,
|
||||
this.isBaseShiny(useIllusion),
|
||||
@ -990,15 +1000,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
getFusionIconAtlasKey(ignoreOverride = false, useIllusion = true): string {
|
||||
// TODO: confirm the correct behavior here (is it intentional that the check fails if `illusion.fusionFormIndex` is `0`?)
|
||||
const fusionFormIndex =
|
||||
useIllusion && this.summonData.illusion?.fusionFormIndex
|
||||
? this.summonData.illusion.fusionFormIndex
|
||||
: this.fusionFormIndex;
|
||||
const fusionVariant =
|
||||
!useIllusion && this.summonData.illusion
|
||||
? this.summonData.illusion.basePokemon.fusionVariant
|
||||
: this.fusionVariant;
|
||||
const illusion = this.summonData.illusion;
|
||||
const { fusionFormIndex, fusionVariant } = useIllusion && illusion ? illusion : this;
|
||||
return this.getFusionSpeciesForm(ignoreOverride, useIllusion).getIconAtlasKey(
|
||||
fusionFormIndex,
|
||||
this.isFusionShiny(),
|
||||
@ -1006,11 +1009,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
);
|
||||
}
|
||||
|
||||
getIconId(ignoreOverride?: boolean, useIllusion = true): string {
|
||||
const formIndex =
|
||||
useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion?.formIndex : this.formIndex;
|
||||
const variant =
|
||||
!useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.variant : this.variant;
|
||||
getIconId(ignoreOverride?: boolean, useIllusion = false): string {
|
||||
const illusion = this.summonData.illusion;
|
||||
const { formIndex, variant } = useIllusion && illusion ? illusion : this;
|
||||
return this.getSpeciesForm(ignoreOverride, useIllusion).getIconId(
|
||||
this.getGender(ignoreOverride, useIllusion) === Gender.FEMALE,
|
||||
formIndex,
|
||||
@ -1020,14 +1021,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
getFusionIconId(ignoreOverride?: boolean, useIllusion = true): string {
|
||||
const fusionFormIndex =
|
||||
useIllusion && this.summonData.illusion?.fusionFormIndex
|
||||
? this.summonData.illusion?.fusionFormIndex
|
||||
: this.fusionFormIndex;
|
||||
const fusionVariant =
|
||||
!useIllusion && !!this.summonData.illusion
|
||||
? this.summonData.illusion?.basePokemon.fusionVariant
|
||||
: this.fusionVariant;
|
||||
const illusion = this.summonData.illusion;
|
||||
const { fusionFormIndex, fusionVariant } = useIllusion && illusion ? illusion : this;
|
||||
return this.getFusionSpeciesForm(ignoreOverride, useIllusion).getIconId(
|
||||
this.getFusionGender(ignoreOverride, useIllusion) === Gender.FEMALE,
|
||||
fusionFormIndex,
|
||||
@ -1702,29 +1697,18 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @returns Whether this Pokemon is shiny
|
||||
*/
|
||||
isShiny(useIllusion = false): boolean {
|
||||
if (!useIllusion && this.summonData.illusion) {
|
||||
return (
|
||||
this.summonData.illusion.basePokemon?.shiny ||
|
||||
(this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) ||
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny);
|
||||
return this.isBaseShiny(useIllusion) || this.isFusionShiny(useIllusion);
|
||||
}
|
||||
|
||||
isBaseShiny(useIllusion = false) {
|
||||
if (!useIllusion && this.summonData.illusion) {
|
||||
return !!this.summonData.illusion.basePokemon?.shiny;
|
||||
}
|
||||
return this.shiny;
|
||||
return useIllusion ? (this.summonData.illusion?.shiny ?? this.shiny) : this.shiny;
|
||||
}
|
||||
|
||||
isFusionShiny(useIllusion = false) {
|
||||
if (!useIllusion && this.summonData.illusion) {
|
||||
return !!this.summonData.illusion.basePokemon?.fusionShiny;
|
||||
if (!this.isFusion(useIllusion)) {
|
||||
return false;
|
||||
}
|
||||
return this.isFusion(useIllusion) && this.fusionShiny;
|
||||
return useIllusion ? (this.summonData.illusion?.fusionShiny ?? this.fusionShiny) : this.fusionShiny;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1733,39 +1717,48 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @returns Whether this pokemon's base and fusion counterparts are both shiny.
|
||||
*/
|
||||
isDoubleShiny(useIllusion = false): boolean {
|
||||
if (!useIllusion && this.summonData.illusion?.basePokemon) {
|
||||
return (
|
||||
this.isFusion(false) &&
|
||||
this.summonData.illusion.basePokemon.shiny &&
|
||||
this.summonData.illusion.basePokemon.fusionShiny
|
||||
);
|
||||
}
|
||||
|
||||
return this.isFusion(useIllusion) && this.shiny && this.fusionShiny;
|
||||
return this.isFusion(useIllusion) && this.isBaseShiny(useIllusion) && this.isFusionShiny(useIllusion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this Pokemon's {@linkcode Variant | shiny variant}.
|
||||
* If a fusion, returns the maximum of the two variants.
|
||||
* Only meaningful if this pokemon is actually shiny.
|
||||
* @param useIllusion - Whether to consider this pokemon's illusion if present; default `false`
|
||||
* @returns The shiny variant of this Pokemon.
|
||||
*/
|
||||
getVariant(useIllusion = false): Variant {
|
||||
if (!useIllusion && this.summonData.illusion) {
|
||||
return !this.isFusion(false)
|
||||
? this.summonData.illusion.basePokemon!.variant
|
||||
: (Math.max(this.variant, this.fusionVariant) as Variant);
|
||||
const illusion = this.summonData.illusion;
|
||||
const baseVariant = useIllusion ? (illusion?.variant ?? this.variant) : this.variant;
|
||||
if (!this.isFusion(useIllusion)) {
|
||||
return baseVariant;
|
||||
}
|
||||
|
||||
return !this.isFusion(true) ? this.variant : (Math.max(this.variant, this.fusionVariant) as Variant);
|
||||
const fusionVariant = useIllusion ? (illusion?.fusionVariant ?? this.fusionVariant) : this.fusionVariant;
|
||||
return Math.max(baseVariant, fusionVariant) as Variant;
|
||||
}
|
||||
|
||||
// TODO: Clarify how this differs from `getVariant`
|
||||
getBaseVariant(doubleShiny: boolean): Variant {
|
||||
if (doubleShiny) {
|
||||
return this.summonData.illusion?.basePokemon?.variant ?? this.variant;
|
||||
/**
|
||||
* Return the base pokemon's variant. Equivalent to {@linkcode getVariant} if this pokemon is not a fusion.
|
||||
* @returns The shiny variant of this Pokemon's base species.
|
||||
*/
|
||||
getBaseVariant(useIllusion = false): Variant {
|
||||
const illusion = this.summonData.illusion;
|
||||
return useIllusion && illusion ? (illusion.variant ?? this.variant) : this.variant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fused pokemon's variant.
|
||||
*
|
||||
* @remarks
|
||||
* Always returns `0` if the pokemon is not a fusion.
|
||||
* @returns The shiny variant of this pokemon's fusion species.
|
||||
*/
|
||||
getFusionVariant(useIllusion = false): Variant {
|
||||
if (!this.isFusion(useIllusion)) {
|
||||
return 0;
|
||||
}
|
||||
return this.getVariant();
|
||||
const illusion = this.summonData.illusion;
|
||||
return illusion ? (illusion.fusionVariant ?? this.fusionVariant) : this.fusionVariant;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1782,7 +1775,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @returns Whether this Pokemon is currently fused with another species.
|
||||
*/
|
||||
isFusion(useIllusion = false): boolean {
|
||||
return useIllusion && this.summonData.illusion ? !!this.summonData.illusion.fusionSpecies : !!this.fusionSpecies;
|
||||
return useIllusion ? !!this.summonData.illusion?.fusionSpecies : !!this.fusionSpecies;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1792,9 +1785,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @see {@linkcode getNameToRender} - gets this Pokemon's display name.
|
||||
*/
|
||||
getName(useIllusion = false): string {
|
||||
return !useIllusion && this.summonData.illusion?.basePokemon
|
||||
? this.summonData.illusion.basePokemon.name
|
||||
: this.name;
|
||||
return useIllusion ? (this.summonData.illusion?.name ?? this.name) : this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -5676,7 +5667,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
export class PlayerPokemon extends Pokemon {
|
||||
protected battleInfo: PlayerBattleInfo;
|
||||
protected declare battleInfo: PlayerBattleInfo;
|
||||
public compatibleTms: MoveId[];
|
||||
|
||||
constructor(
|
||||
@ -6205,7 +6196,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
}
|
||||
|
||||
export class EnemyPokemon extends Pokemon {
|
||||
protected battleInfo: EnemyBattleInfo;
|
||||
protected declare battleInfo: EnemyBattleInfo;
|
||||
public trainerSlot: TrainerSlot;
|
||||
public aiType: AiType;
|
||||
public bossSegments: number;
|
||||
|
@ -23,13 +23,13 @@ import {
|
||||
} from "#trainers/trainer-party-template";
|
||||
import { randSeedInt, randSeedItem, randSeedWeightedItem } from "#utils/common";
|
||||
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
||||
import { toSnakeCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
|
||||
export class Trainer extends Phaser.GameObjects.Container {
|
||||
public config: TrainerConfig;
|
||||
public variant: TrainerVariant;
|
||||
public partyTemplateIndex: number;
|
||||
public name: string;
|
||||
public partnerName: string;
|
||||
public nameKey: string;
|
||||
public partnerNameKey: string | undefined;
|
||||
@ -170,7 +170,7 @@ export class Trainer extends Phaser.GameObjects.Container {
|
||||
const evilTeamTitles = ["grunt"];
|
||||
if (this.name === "" && evilTeamTitles.some(t => name.toLocaleLowerCase().includes(t))) {
|
||||
// This is a evil team grunt so we localize it by only using the "name" as the title
|
||||
title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`);
|
||||
title = i18next.t(`trainerClasses:${toSnakeCase(name)}`);
|
||||
console.log("Localized grunt name: " + title);
|
||||
// Since grunts are not named we can just return the title
|
||||
return title;
|
||||
@ -187,7 +187,7 @@ export class Trainer extends Phaser.GameObjects.Container {
|
||||
}
|
||||
// Get the localized trainer class name from the i18n file and set it as the title.
|
||||
// This is used for trainer class names, not titles like "Elite Four, Champion, etc."
|
||||
title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`);
|
||||
title = i18next.t(`trainerClasses:${toSnakeCase(name)}`);
|
||||
}
|
||||
|
||||
// If no specific trainer slot is set.
|
||||
@ -208,7 +208,7 @@ export class Trainer extends Phaser.GameObjects.Container {
|
||||
|
||||
if (this.config.titleDouble && this.variant === TrainerVariant.DOUBLE && !this.config.doubleOnly) {
|
||||
title = this.config.titleDouble;
|
||||
name = i18next.t(`trainerNames:${this.config.nameDouble.toLowerCase().replace(/\s/g, "_")}`);
|
||||
name = i18next.t(`trainerNames:${toSnakeCase(this.config.nameDouble)}`);
|
||||
}
|
||||
|
||||
console.log(title ? `${title} ${name}` : name);
|
||||
|
35
src/init/init.ts
Normal file
35
src/init/init.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { initAbilities } from "#abilities/ability";
|
||||
import { initBiomes } from "#balance/biomes";
|
||||
import { initEggMoves } from "#balance/egg-moves";
|
||||
import { initPokemonPrevolutions, initPokemonStarters } from "#balance/pokemon-evolutions";
|
||||
import { initChallenges } from "#data/challenge";
|
||||
import { initTrainerTypeDialogue } from "#data/dialogue";
|
||||
import { initPokemonForms } from "#data/pokemon-forms";
|
||||
import { initSpecies } from "#data/pokemon-species";
|
||||
import { initModifierPools } from "#modifiers/init-modifier-pools";
|
||||
import { initModifierTypes } from "#modifiers/modifier-type";
|
||||
import { initMoves } from "#moves/move";
|
||||
import { initMysteryEncounters } from "#mystery-encounters/mystery-encounters";
|
||||
import { initAchievements } from "#system/achv";
|
||||
import { initVouchers } from "#system/voucher";
|
||||
import { initStatsKeys } from "#ui/game-stats-ui-handler";
|
||||
|
||||
/** Initialize the game. */
|
||||
export function initializeGame() {
|
||||
initModifierTypes();
|
||||
initModifierPools();
|
||||
initAchievements();
|
||||
initVouchers();
|
||||
initStatsKeys();
|
||||
initPokemonPrevolutions();
|
||||
initPokemonStarters();
|
||||
initBiomes();
|
||||
initEggMoves();
|
||||
initPokemonForms();
|
||||
initTrainerTypeDialogue();
|
||||
initSpecies();
|
||||
initMoves();
|
||||
initAbilities();
|
||||
initChallenges();
|
||||
initMysteryEncounters();
|
||||
}
|
@ -3,13 +3,13 @@ import { TouchControl } from "#app/touch-controls";
|
||||
import { Button } from "#enums/buttons";
|
||||
import { Device } from "#enums/devices";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import cfg_keyboard_qwerty from "#inputs/cfg_keyboard_qwerty";
|
||||
import { assign, getButtonWithKeycode, getIconForLatestInput, swap } from "#inputs/configHandler";
|
||||
import pad_dualshock from "#inputs/pad_dualshock";
|
||||
import pad_generic from "#inputs/pad_generic";
|
||||
import pad_procon from "#inputs/pad_procon";
|
||||
import pad_unlicensedSNES from "#inputs/pad_unlicensedSNES";
|
||||
import pad_xbox360 from "#inputs/pad_xbox360";
|
||||
import cfg_keyboard_qwerty from "#inputs/cfg-keyboard-qwerty";
|
||||
import { assign, getButtonWithKeycode, getIconForLatestInput, swap } from "#inputs/config-handler";
|
||||
import pad_dualshock from "#inputs/pad-dualshock";
|
||||
import pad_generic from "#inputs/pad-generic";
|
||||
import pad_procon from "#inputs/pad-procon";
|
||||
import pad_unlicensedSNES from "#inputs/pad-unlicensed-snes";
|
||||
import pad_xbox360 from "#inputs/pad-xbox360";
|
||||
import type { SettingGamepad } from "#system/settings-gamepad";
|
||||
import type { SettingKeyboard } from "#system/settings-keyboard";
|
||||
import { MoveTouchControlsHandler } from "#ui/move-touch-controls-handler";
|
||||
|
@ -1,29 +1,16 @@
|
||||
import { initAbilities } from "#abilities/ability";
|
||||
import { timedEventManager } from "#app/global-event-manager";
|
||||
import { initializeGame } from "#app/init/init";
|
||||
import { SceneBase } from "#app/scene-base";
|
||||
import { isMobile } from "#app/touch-controls";
|
||||
import { initBiomes } from "#balance/biomes";
|
||||
import { initEggMoves } from "#balance/egg-moves";
|
||||
import { initPokemonPrevolutions, initPokemonStarters } from "#balance/pokemon-evolutions";
|
||||
import { initChallenges } from "#data/challenge";
|
||||
import { initTrainerTypeDialogue } from "#data/dialogue";
|
||||
import { initPokemonForms } from "#data/pokemon-forms";
|
||||
import { initSpecies } from "#data/pokemon-species";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { GachaType } from "#enums/gacha-types";
|
||||
import { getBiomeHasProps } from "#field/arena";
|
||||
import { initModifierPools } from "#modifiers/init-modifier-pools";
|
||||
import { initModifierTypes } from "#modifiers/modifier-type";
|
||||
import { initMoves } from "#moves/move";
|
||||
import { initMysteryEncounters } from "#mystery-encounters/mystery-encounters";
|
||||
import { CacheBustedLoaderPlugin } from "#plugins/cache-busted-loader-plugin";
|
||||
import { initAchievements } from "#system/achv";
|
||||
import { initVouchers } from "#system/voucher";
|
||||
import { initStatsKeys } from "#ui/game-stats-ui-handler";
|
||||
import { getWindowVariantSuffix, WindowVariant } from "#ui/ui-theme";
|
||||
import { hasAllLocalizedSprites, localPing } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
import i18next from "i18next";
|
||||
import type { GameObjects } from "phaser";
|
||||
|
||||
export class LoadingScene extends SceneBase {
|
||||
public static readonly KEY = "loading";
|
||||
@ -369,30 +356,12 @@ export class LoadingScene extends SceneBase {
|
||||
|
||||
this.loadLoadingScreen();
|
||||
|
||||
initModifierTypes();
|
||||
initModifierPools();
|
||||
|
||||
initAchievements();
|
||||
initVouchers();
|
||||
initStatsKeys();
|
||||
initPokemonPrevolutions();
|
||||
initPokemonStarters();
|
||||
initBiomes();
|
||||
initEggMoves();
|
||||
initPokemonForms();
|
||||
initTrainerTypeDialogue();
|
||||
initSpecies();
|
||||
initMoves();
|
||||
initAbilities();
|
||||
initChallenges();
|
||||
initMysteryEncounters();
|
||||
initializeGame();
|
||||
}
|
||||
|
||||
loadLoadingScreen() {
|
||||
const mobile = isMobile();
|
||||
|
||||
const loadingGraphics: any[] = [];
|
||||
|
||||
const bg = this.add.image(0, 0, "");
|
||||
bg.setOrigin(0, 0);
|
||||
bg.setScale(6);
|
||||
@ -463,6 +432,7 @@ export class LoadingScene extends SceneBase {
|
||||
});
|
||||
disclaimerDescriptionText.setOrigin(0.5, 0.5);
|
||||
|
||||
const loadingGraphics: (GameObjects.Image | GameObjects.Graphics | GameObjects.Text)[] = [];
|
||||
loadingGraphics.push(
|
||||
bg,
|
||||
graphics,
|
||||
|
@ -23,6 +23,7 @@ import type { PokemonType } from "#enums/pokemon-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { BATTLE_STATS, type PermanentStat, Stat, TEMP_BATTLE_STATS, type TempBattleStat } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||
import type {
|
||||
DoubleBattleChanceBoosterModifierType,
|
||||
@ -40,7 +41,7 @@ import type {
|
||||
} from "#modifiers/modifier-type";
|
||||
import type { VoucherType } from "#system/voucher";
|
||||
import type { ModifierInstanceMap, ModifierString } from "#types/modifier-types";
|
||||
import { addTextObject, TextStyle } from "#ui/text";
|
||||
import { addTextObject } from "#ui/text";
|
||||
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#utils/common";
|
||||
import { getModifierType } from "#utils/modifier-utils";
|
||||
import i18next from "i18next";
|
||||
@ -461,7 +462,7 @@ export abstract class LapsingPersistentModifier extends PersistentModifier {
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier {
|
||||
public override type: DoubleBattleChanceBoosterModifierType;
|
||||
public declare type: DoubleBattleChanceBoosterModifierType;
|
||||
|
||||
match(modifier: Modifier): boolean {
|
||||
return modifier instanceof DoubleBattleChanceBoosterModifier && modifier.getMaxBattles() === this.getMaxBattles();
|
||||
@ -935,7 +936,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier {
|
||||
* Currently used by Shuckle Juice item
|
||||
*/
|
||||
export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
public override type: PokemonBaseStatTotalModifierType;
|
||||
public declare type: PokemonBaseStatTotalModifierType;
|
||||
public isTransferable = false;
|
||||
public statModifier: 10 | -15;
|
||||
|
||||
@ -2073,7 +2074,7 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier {
|
||||
}
|
||||
|
||||
export class TerrastalizeModifier extends ConsumablePokemonModifier {
|
||||
public override type: TerastallizeModifierType;
|
||||
public declare type: TerastallizeModifierType;
|
||||
public teraType: PokemonType;
|
||||
|
||||
constructor(type: TerastallizeModifierType, pokemonId: number, teraType: PokemonType) {
|
||||
@ -2317,7 +2318,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier {
|
||||
}
|
||||
|
||||
export class TmModifier extends ConsumablePokemonModifier {
|
||||
public override type: TmModifierType;
|
||||
public declare type: TmModifierType;
|
||||
|
||||
/**
|
||||
* Applies {@linkcode TmModifier}
|
||||
@ -2364,7 +2365,7 @@ export class RememberMoveModifier extends ConsumablePokemonModifier {
|
||||
}
|
||||
|
||||
export class EvolutionItemModifier extends ConsumablePokemonModifier {
|
||||
public override type: EvolutionItemModifierType;
|
||||
public declare type: EvolutionItemModifierType;
|
||||
/**
|
||||
* Applies {@linkcode EvolutionItemModifier}
|
||||
* @param playerPokemon The {@linkcode PlayerPokemon} that should evolve via item
|
||||
@ -2529,7 +2530,7 @@ export class ExpBoosterModifier extends PersistentModifier {
|
||||
}
|
||||
|
||||
export class PokemonExpBoosterModifier extends PokemonHeldItemModifier {
|
||||
public override type: PokemonExpBoosterModifierType;
|
||||
public declare type: PokemonExpBoosterModifierType;
|
||||
|
||||
private boostMultiplier: number;
|
||||
|
||||
@ -2626,7 +2627,7 @@ export class ExpBalanceModifier extends PersistentModifier {
|
||||
}
|
||||
|
||||
export class PokemonFriendshipBoosterModifier extends PokemonHeldItemModifier {
|
||||
public override type: PokemonFriendshipBoosterModifierType;
|
||||
public declare type: PokemonFriendshipBoosterModifierType;
|
||||
|
||||
matchType(modifier: Modifier): boolean {
|
||||
return modifier instanceof PokemonFriendshipBoosterModifier;
|
||||
@ -2683,7 +2684,7 @@ export class PokemonNatureWeightModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
|
||||
export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier {
|
||||
public override type: PokemonMoveAccuracyBoosterModifierType;
|
||||
public declare type: PokemonMoveAccuracyBoosterModifierType;
|
||||
private accuracyAmount: number;
|
||||
|
||||
constructor(type: PokemonMoveAccuracyBoosterModifierType, pokemonId: number, accuracy: number, stackCount?: number) {
|
||||
@ -2735,7 +2736,7 @@ export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier
|
||||
}
|
||||
|
||||
export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
|
||||
public override type: PokemonMultiHitModifierType;
|
||||
public declare type: PokemonMultiHitModifierType;
|
||||
|
||||
matchType(modifier: Modifier): boolean {
|
||||
return modifier instanceof PokemonMultiHitModifier;
|
||||
@ -2816,7 +2817,7 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
|
||||
export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier {
|
||||
public override type: FormChangeItemModifierType;
|
||||
public declare type: FormChangeItemModifierType;
|
||||
public formChangeItem: FormChangeItem;
|
||||
public active: boolean;
|
||||
public isTransferable = false;
|
||||
|
@ -279,6 +279,7 @@ export class AttemptCapturePhase extends PokemonPhase {
|
||||
globalScene.updateModifiers(true);
|
||||
removePokemon();
|
||||
if (newPokemon) {
|
||||
newPokemon.leaveField(true, true, false);
|
||||
newPokemon.loadAssets().then(end);
|
||||
} else {
|
||||
end();
|
||||
|
@ -2,7 +2,6 @@ import type { TurnCommand } from "#app/battle";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { speciesStarterCosts } from "#balance/starters";
|
||||
import type { EncoreTag } from "#data/battler-tags";
|
||||
import { TrappedTag } from "#data/battler-tags";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||
@ -22,59 +21,77 @@ import type { MoveTargetSet } from "#moves/move";
|
||||
import { getMoveTargets } from "#moves/move-utils";
|
||||
import { FieldPhase } from "#phases/field-phase";
|
||||
import type { TurnMove } from "#types/turn-move";
|
||||
import { isNullOrUndefined } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
export class CommandPhase extends FieldPhase {
|
||||
public readonly phaseName = "CommandPhase";
|
||||
protected fieldIndex: number;
|
||||
|
||||
/**
|
||||
* Whether the command phase is handling a switch command
|
||||
*/
|
||||
private isSwitch = false;
|
||||
|
||||
constructor(fieldIndex: number) {
|
||||
super();
|
||||
|
||||
this.fieldIndex = fieldIndex;
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
globalScene.updateGameInfo();
|
||||
|
||||
/**
|
||||
* Resets the cursor to the position of {@linkcode Command.FIGHT} if any of the following are true
|
||||
* - The setting to remember the last action is not enabled
|
||||
* - This is the first turn of a mystery encounter, trainer battle, or the END biome
|
||||
* - The cursor is currently on the POKEMON command
|
||||
*/
|
||||
private resetCursorIfNeeded(): void {
|
||||
const commandUiHandler = globalScene.ui.handlers[UiMode.COMMAND];
|
||||
const { arena, commandCursorMemory, currentBattle } = globalScene;
|
||||
const { battleType, turn } = currentBattle;
|
||||
const { biomeType } = arena;
|
||||
|
||||
// If one of these conditions is true, we always reset the cursor to Command.FIGHT
|
||||
const cursorResetEvent =
|
||||
globalScene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER ||
|
||||
globalScene.currentBattle.battleType === BattleType.TRAINER ||
|
||||
globalScene.arena.biomeType === BiomeId.END;
|
||||
battleType === BattleType.MYSTERY_ENCOUNTER || battleType === BattleType.TRAINER || biomeType === BiomeId.END;
|
||||
|
||||
if (commandUiHandler) {
|
||||
if (
|
||||
(globalScene.currentBattle.turn === 1 && (!globalScene.commandCursorMemory || cursorResetEvent)) ||
|
||||
commandUiHandler.getCursor() === Command.POKEMON
|
||||
) {
|
||||
commandUiHandler.setCursor(Command.FIGHT);
|
||||
} else {
|
||||
commandUiHandler.setCursor(commandUiHandler.getCursor());
|
||||
}
|
||||
if (!commandUiHandler) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
(turn === 1 && (!commandCursorMemory || cursorResetEvent)) ||
|
||||
commandUiHandler.getCursor() === Command.POKEMON
|
||||
) {
|
||||
commandUiHandler.setCursor(Command.FIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submethod of {@linkcode start} that validates field index logic for nonzero field indices.
|
||||
* Must only be called if the field index is nonzero.
|
||||
*/
|
||||
private handleFieldIndexLogic(): void {
|
||||
// If we somehow are attempting to check the right pokemon but there's only one pokemon out
|
||||
// Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching
|
||||
// TODO: Prevent this from happening in the first place
|
||||
if (globalScene.getPlayerField().filter(p => p.isActive()).length === 1) {
|
||||
this.fieldIndex = FieldPosition.CENTER;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.fieldIndex) {
|
||||
// If we somehow are attempting to check the right pokemon but there's only one pokemon out
|
||||
// Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching
|
||||
if (globalScene.getPlayerField().filter(p => p.isActive()).length === 1) {
|
||||
this.fieldIndex = FieldPosition.CENTER;
|
||||
} else {
|
||||
const allyCommand = globalScene.currentBattle.turnCommands[this.fieldIndex - 1];
|
||||
if (allyCommand?.command === Command.BALL || allyCommand?.command === Command.RUN) {
|
||||
globalScene.currentBattle.turnCommands[this.fieldIndex] = {
|
||||
command: allyCommand?.command,
|
||||
skip: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
const allyCommand = globalScene.currentBattle.turnCommands[this.fieldIndex - 1];
|
||||
if (allyCommand?.command === Command.BALL || allyCommand?.command === Command.RUN) {
|
||||
globalScene.currentBattle.turnCommands[this.fieldIndex] = {
|
||||
command: allyCommand?.command,
|
||||
skip: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submethod of {@linkcode start} that sets the turn command to skip if this pokemon
|
||||
* is commanding its ally via {@linkcode AbilityId.COMMANDER}.
|
||||
*/
|
||||
private checkCommander(): void {
|
||||
// If the Pokemon has applied Commander's effects to its ally, skip this command
|
||||
if (
|
||||
globalScene.currentBattle?.double &&
|
||||
@ -86,377 +103,521 @@ export class CommandPhase extends FieldPhase {
|
||||
skip: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the Pokemon is under the effects of Encore. If so, Encore can end early if the encored move has no more PP.
|
||||
const encoreTag = this.getPokemon().getTag(BattlerTagType.ENCORE) as EncoreTag | undefined;
|
||||
if (encoreTag) {
|
||||
this.getPokemon().lapseTag(BattlerTagType.ENCORE);
|
||||
}
|
||||
|
||||
if (globalScene.currentBattle.turnCommands[this.fieldIndex]?.skip) {
|
||||
return this.end();
|
||||
}
|
||||
|
||||
const playerPokemon = globalScene.getPlayerField()[this.fieldIndex];
|
||||
|
||||
/**
|
||||
* Clear out all unusable moves in front of the currently acting pokemon's move queue.
|
||||
*/
|
||||
// TODO: Refactor move queue handling to ensure that this method is not necessary.
|
||||
private clearUnusuableMoves(): void {
|
||||
const playerPokemon = this.getPokemon();
|
||||
const moveQueue = playerPokemon.getMoveQueue();
|
||||
|
||||
while (
|
||||
moveQueue.length &&
|
||||
moveQueue[0] &&
|
||||
moveQueue[0].move &&
|
||||
!isVirtual(moveQueue[0].useMode) &&
|
||||
(!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move) ||
|
||||
!playerPokemon
|
||||
.getMoveset()
|
||||
[playerPokemon.getMoveset().findIndex(m => m.moveId === moveQueue[0].move)].isUsable(
|
||||
playerPokemon,
|
||||
isIgnorePP(moveQueue[0].useMode),
|
||||
))
|
||||
) {
|
||||
moveQueue.shift();
|
||||
if (moveQueue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Refactor this. I did a few simple find/replace matches but this is just ABHORRENTLY structured
|
||||
if (moveQueue.length > 0) {
|
||||
const queuedMove = moveQueue[0];
|
||||
if (!queuedMove.move) {
|
||||
this.handleCommand(Command.FIGHT, -1, MoveUseMode.NORMAL);
|
||||
} else {
|
||||
const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move);
|
||||
if (
|
||||
(moveIndex > -1 &&
|
||||
playerPokemon.getMoveset()[moveIndex].isUsable(playerPokemon, isIgnorePP(queuedMove.useMode))) ||
|
||||
isVirtual(queuedMove.useMode)
|
||||
) {
|
||||
this.handleCommand(Command.FIGHT, moveIndex, queuedMove.useMode, queuedMove);
|
||||
} else {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let entriesToDelete = 0;
|
||||
const moveset = playerPokemon.getMoveset();
|
||||
for (const queuedMove of moveQueue) {
|
||||
const movesetQueuedMove = moveset.find(m => m.moveId === queuedMove.move);
|
||||
if (
|
||||
globalScene.currentBattle.isBattleMysteryEncounter() &&
|
||||
globalScene.currentBattle.mysteryEncounter?.skipToFightInput
|
||||
queuedMove.move !== MoveId.NONE &&
|
||||
!isVirtual(queuedMove.useMode) &&
|
||||
!movesetQueuedMove?.isUsable(playerPokemon, isIgnorePP(queuedMove.useMode))
|
||||
) {
|
||||
globalScene.ui.clearText();
|
||||
globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex);
|
||||
entriesToDelete++;
|
||||
} else {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (entriesToDelete) {
|
||||
moveQueue.splice(0, entriesToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Remove `args` and clean this thing up
|
||||
* Code will need to be copied over from pkty except replacing the `virtual` and `ignorePP` args with a corresponding `MoveUseMode`.
|
||||
* Attempt to execute the first usable move in this Pokemon's move queue
|
||||
* @returns Whether a queued move was successfully set to be executed.
|
||||
*/
|
||||
handleCommand(command: Command, cursor: number, ...args: any[]): boolean {
|
||||
private tryExecuteQueuedMove(): boolean {
|
||||
this.clearUnusuableMoves();
|
||||
const playerPokemon = globalScene.getPlayerField()[this.fieldIndex];
|
||||
const moveQueue = playerPokemon.getMoveQueue();
|
||||
|
||||
if (moveQueue.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const queuedMove = moveQueue[0];
|
||||
if (queuedMove.move === MoveId.NONE) {
|
||||
this.handleCommand(Command.FIGHT, -1);
|
||||
return true;
|
||||
}
|
||||
const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move);
|
||||
if (!isVirtual(queuedMove.useMode) && moveIndex === -1) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
} else {
|
||||
this.handleCommand(Command.FIGHT, moveIndex, queuedMove.useMode, queuedMove);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override start(): void {
|
||||
super.start();
|
||||
|
||||
globalScene.updateGameInfo();
|
||||
this.resetCursorIfNeeded();
|
||||
|
||||
if (this.fieldIndex) {
|
||||
this.handleFieldIndexLogic();
|
||||
}
|
||||
|
||||
this.checkCommander();
|
||||
|
||||
const playerPokemon = this.getPokemon();
|
||||
|
||||
// Note: It is OK to call this if the target is not under the effect of encore; it will simply do nothing.
|
||||
playerPokemon.lapseTag(BattlerTagType.ENCORE);
|
||||
|
||||
if (globalScene.currentBattle.turnCommands[this.fieldIndex]?.skip) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.tryExecuteQueuedMove()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
globalScene.currentBattle.isBattleMysteryEncounter() &&
|
||||
globalScene.currentBattle.mysteryEncounter?.skipToFightInput
|
||||
) {
|
||||
globalScene.ui.clearText();
|
||||
globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex);
|
||||
} else {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submethod of {@linkcode handleFightCommand} responsible for queuing the appropriate
|
||||
* error message when a move cannot be used.
|
||||
* @param user - The pokemon using the move
|
||||
* @param cursor - The index of the move in the moveset
|
||||
*/
|
||||
private queueFightErrorMessage(user: PlayerPokemon, cursor: number) {
|
||||
const move = user.getMoveset()[cursor];
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
|
||||
// Decides between a Disabled, Not Implemented, or No PP translation message
|
||||
const errorMessage = user.isMoveRestricted(move.moveId, user)
|
||||
? user.getRestrictingTag(move.moveId, user)!.selectionDeniedText(user, move.moveId)
|
||||
: move.getName().endsWith(" (N)")
|
||||
? "battle:moveNotImplemented"
|
||||
: "battle:moveNoPP";
|
||||
const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator
|
||||
|
||||
globalScene.ui.showText(
|
||||
i18next.t(errorMessage, { moveName: moveName }),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.clearText();
|
||||
globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex);
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for {@linkcode handleFightCommand} that returns the moveID for the phase
|
||||
* based on the move passed in or the cursor.
|
||||
*
|
||||
* Does not check if the move is usable or not, that should be handled by the caller.
|
||||
*/
|
||||
private computeMoveId(playerPokemon: PlayerPokemon, cursor: number, move: TurnMove | undefined): MoveId {
|
||||
return move?.move ?? (cursor > -1 ? playerPokemon.getMoveset()[cursor]?.moveId : MoveId.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the logic for executing a fight-related command
|
||||
*
|
||||
* @remarks
|
||||
* - Validates whether the move can be used, using struggle if not
|
||||
* - Constructs the turn command and inserts it into the battle's turn commands
|
||||
*
|
||||
* @param command - The command to handle (FIGHT or TERA)
|
||||
* @param cursor - The index that the cursor is placed on, or -1 if no move can be selected.
|
||||
* @param ignorePP - Whether to ignore PP when checking if the move can be used.
|
||||
* @param move - The move to force the command to use, if any.
|
||||
*/
|
||||
private handleFightCommand(
|
||||
command: Command.FIGHT | Command.TERA,
|
||||
cursor: number,
|
||||
useMode: MoveUseMode = MoveUseMode.NORMAL,
|
||||
move?: TurnMove,
|
||||
): boolean {
|
||||
const playerPokemon = this.getPokemon();
|
||||
const ignorePP = isIgnorePP(useMode);
|
||||
|
||||
let canUse = cursor === -1 || playerPokemon.trySelectMove(cursor, ignorePP);
|
||||
|
||||
// Ternary here ensures we don't compute struggle conditions unless necessary
|
||||
const useStruggle = canUse
|
||||
? false
|
||||
: cursor > -1 && !playerPokemon.getMoveset().some(m => m.isUsable(playerPokemon));
|
||||
|
||||
canUse ||= useStruggle;
|
||||
|
||||
if (!canUse) {
|
||||
this.queueFightErrorMessage(playerPokemon, cursor);
|
||||
return false;
|
||||
}
|
||||
|
||||
const moveId = useStruggle ? MoveId.STRUGGLE : this.computeMoveId(playerPokemon, cursor, move);
|
||||
|
||||
const turnCommand: TurnCommand = {
|
||||
command: Command.FIGHT,
|
||||
cursor,
|
||||
move: { move: moveId, targets: [], useMode },
|
||||
args: [useMode, move],
|
||||
};
|
||||
const preTurnCommand: TurnCommand = {
|
||||
command,
|
||||
targets: [this.fieldIndex],
|
||||
skip: command === Command.FIGHT,
|
||||
};
|
||||
|
||||
const moveTargets: MoveTargetSet =
|
||||
move === undefined
|
||||
? getMoveTargets(playerPokemon, moveId)
|
||||
: {
|
||||
targets: move.targets,
|
||||
multiple: move.targets.length > 1,
|
||||
};
|
||||
|
||||
if (moveId === MoveId.NONE) {
|
||||
turnCommand.targets = [this.fieldIndex];
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Move:",
|
||||
MoveId[moveId],
|
||||
"Move targets:",
|
||||
moveTargets,
|
||||
"\nPlayer Pokemon:",
|
||||
getPokemonNameWithAffix(playerPokemon),
|
||||
);
|
||||
|
||||
if (moveTargets.targets.length > 1 && moveTargets.multiple) {
|
||||
globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex);
|
||||
}
|
||||
|
||||
if (turnCommand.move && (moveTargets.targets.length <= 1 || moveTargets.multiple)) {
|
||||
turnCommand.move.targets = moveTargets.targets;
|
||||
} else if (
|
||||
turnCommand.move &&
|
||||
playerPokemon.getTag(BattlerTagType.CHARGING) &&
|
||||
playerPokemon.getMoveQueue().length >= 1
|
||||
) {
|
||||
turnCommand.move.targets = playerPokemon.getMoveQueue()[0].targets;
|
||||
} else {
|
||||
globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex);
|
||||
}
|
||||
|
||||
globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand;
|
||||
globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode in preparation to show the text, and then show the text.
|
||||
* Only works for parameterless i18next keys.
|
||||
* @param key - The i18next key for the text to show
|
||||
*/
|
||||
private queueShowText(key: string): void {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
|
||||
globalScene.ui.showText(
|
||||
i18next.t(key),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for {@linkcode handleBallCommand} that checks if a pokeball can be thrown
|
||||
* and displays the appropriate error message.
|
||||
*
|
||||
* @remarks
|
||||
* The pokeball may not be thrown if any of the following are true:
|
||||
* - It is a trainer battle
|
||||
* - The player is in the {@linkcode BiomeId.END | End} biome and
|
||||
* - it is not classic mode; or
|
||||
* - the fresh start challenge is active; or
|
||||
* - the player has not caught the target before and the player is still missing more than one starter
|
||||
* - The player is in a mystery encounter that disallows catching the pokemon
|
||||
* @returns Whether a pokeball can be thrown
|
||||
*/
|
||||
private checkCanUseBall(): boolean {
|
||||
const { arena, currentBattle, gameData, gameMode } = globalScene;
|
||||
const { battleType } = currentBattle;
|
||||
const { biomeType } = arena;
|
||||
const { isClassic } = gameMode;
|
||||
const { dexData } = gameData;
|
||||
|
||||
const someUncaughtSpeciesOnField = globalScene
|
||||
.getEnemyField()
|
||||
.some(p => p.isActive() && !dexData[p.species.speciesId].caughtAttr);
|
||||
const missingMultipleStarters =
|
||||
gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1;
|
||||
if (
|
||||
biomeType === BiomeId.END &&
|
||||
(!isClassic || gameMode.isFreshStartChallenge() || (someUncaughtSpeciesOnField && missingMultipleStarters))
|
||||
) {
|
||||
this.queueShowText("battle:noPokeballForce");
|
||||
} else if (battleType === BattleType.TRAINER) {
|
||||
this.queueShowText("battle:noPokeballTrainer");
|
||||
} else if (currentBattle.isBattleMysteryEncounter() && !currentBattle.mysteryEncounter!.catchAllowed) {
|
||||
this.queueShowText("battle:noPokeballMysteryEncounter");
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for {@linkcode handleCommand} that handles the logic when the selected command is to use a pokeball.
|
||||
*
|
||||
* @param cursor - The index of the pokeball to use
|
||||
* @returns Whether the command was successfully initiated
|
||||
*/
|
||||
private handleBallCommand(cursor: number): boolean {
|
||||
const targets = globalScene
|
||||
.getEnemyField()
|
||||
.filter(p => p.isActive(true))
|
||||
.map(p => p.getBattlerIndex());
|
||||
if (targets.length > 1) {
|
||||
this.queueShowText("battle:noPokeballMulti");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.checkCanUseBall()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const numBallTypes = 5;
|
||||
if (cursor < numBallTypes) {
|
||||
const targetPokemon = globalScene.getEnemyPokemon();
|
||||
if (
|
||||
targetPokemon?.isBoss() &&
|
||||
targetPokemon?.bossSegmentIndex >= 1 &&
|
||||
// TODO: Decouple this hardcoded exception for wonder guard and just check the target...
|
||||
!targetPokemon?.hasAbility(AbilityId.WONDER_GUARD, false, true) &&
|
||||
cursor < PokeballType.MASTER_BALL
|
||||
) {
|
||||
this.queueShowText("battle:noPokeballStrong");
|
||||
return false;
|
||||
}
|
||||
|
||||
globalScene.currentBattle.turnCommands[this.fieldIndex] = {
|
||||
command: Command.BALL,
|
||||
cursor: cursor,
|
||||
};
|
||||
globalScene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets;
|
||||
if (this.fieldIndex) {
|
||||
globalScene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submethod of {@linkcode tryLeaveField} to handle the logic for effects that prevent the pokemon from leaving the field
|
||||
* due to trapping abilities or effects.
|
||||
*
|
||||
* This method queues the proper messages in the case of trapping abilities or effects.
|
||||
*
|
||||
* @returns Whether the pokemon is currently trapped
|
||||
*/
|
||||
private handleTrap(): boolean {
|
||||
const playerPokemon = this.getPokemon();
|
||||
const trappedAbMessages: string[] = [];
|
||||
const isSwitch = this.isSwitch;
|
||||
if (!playerPokemon.isTrapped(trappedAbMessages)) {
|
||||
return false;
|
||||
}
|
||||
if (trappedAbMessages.length > 0) {
|
||||
if (isSwitch) {
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
}
|
||||
globalScene.ui.showText(
|
||||
trappedAbMessages[0],
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
if (isSwitch) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
const trapTag = playerPokemon.getTag(TrappedTag);
|
||||
const fairyLockTag = globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER);
|
||||
|
||||
if (!isSwitch) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
}
|
||||
if (trapTag) {
|
||||
this.showNoEscapeText(trapTag, false);
|
||||
} else if (fairyLockTag) {
|
||||
this.showNoEscapeText(fairyLockTag, false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common helper method that attempts to have the pokemon leave the field.
|
||||
* Checks for trapping abilities and effects.
|
||||
*
|
||||
* @param cursor - The index of the option that the cursor is on
|
||||
* @returns Whether the pokemon is able to leave the field, indicating the command phase should end
|
||||
*/
|
||||
private tryLeaveField(cursor?: number, isBatonSwitch = false): boolean {
|
||||
const currentBattle = globalScene.currentBattle;
|
||||
|
||||
if (isBatonSwitch || !this.handleTrap()) {
|
||||
currentBattle.turnCommands[this.fieldIndex] = this.isSwitch
|
||||
? {
|
||||
command: Command.POKEMON,
|
||||
cursor,
|
||||
args: [isBatonSwitch],
|
||||
}
|
||||
: {
|
||||
command: Command.RUN,
|
||||
};
|
||||
if (!this.isSwitch && this.fieldIndex) {
|
||||
currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for {@linkcode handleCommand} that handles the logic when the selected command is RUN.
|
||||
*
|
||||
* @remarks
|
||||
* Checks if the player is allowed to flee, and if not, queues the appropriate message.
|
||||
*
|
||||
* The player cannot flee if:
|
||||
* - The player is in the {@linkcode BiomeId.END | End} biome
|
||||
* - The player is in a trainer battle
|
||||
* - The player is in a mystery encounter that disallows fleeing
|
||||
* - The player's pokemon is trapped by an ability or effect
|
||||
* @returns Whether the pokemon is able to leave the field, indicating the command phase should end
|
||||
*/
|
||||
private handleRunCommand(): boolean {
|
||||
const { currentBattle, arena } = globalScene;
|
||||
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed ?? true;
|
||||
if (arena.biomeType === BiomeId.END || !mysteryEncounterFleeAllowed) {
|
||||
this.queueShowText("battle:noEscapeForce");
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
currentBattle.battleType === BattleType.TRAINER ||
|
||||
currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE
|
||||
) {
|
||||
this.queueShowText("battle:noEscapeTrainer");
|
||||
return false;
|
||||
}
|
||||
|
||||
const success = this.tryLeaveField();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a message indicating that the pokemon cannot escape, and then return to the command phase.
|
||||
*/
|
||||
private showNoEscapeText(tag: any, isSwitch: boolean): void {
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:noEscapePokemon", {
|
||||
pokemonName:
|
||||
tag.sourceId && globalScene.getPokemonById(tag.sourceId)
|
||||
? getPokemonNameWithAffix(globalScene.getPokemonById(tag.sourceId)!)
|
||||
: "",
|
||||
moveName: tag.getMoveName(),
|
||||
escapeVerb: i18next.t(isSwitch ? "battle:escapeVerbSwitch" : "battle:escapeVerbFlee"),
|
||||
}),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
if (!isSwitch) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
// Overloads for handleCommand to provide a more specific signature for the different options
|
||||
/**
|
||||
* Process the command phase logic based on the selected command
|
||||
*
|
||||
* @param command - The kind of command to handle
|
||||
* @param cursor - The index of option that the cursor is on, or -1 if no option is selected
|
||||
* @param useMode - The mode to use for the move, if applicable. For switches, a boolean that specifies whether the switch is a Baton switch.
|
||||
* @param move - For {@linkcode Command.FIGHT}, the move to use
|
||||
* @returns Whether the command was successful
|
||||
*/
|
||||
handleCommand(command: Command.FIGHT | Command.TERA, cursor: number, useMode?: MoveUseMode, move?: TurnMove): boolean;
|
||||
handleCommand(command: Command.BALL, cursor: number): boolean;
|
||||
handleCommand(command: Command.POKEMON, cursor: number, useBaton: boolean): boolean;
|
||||
handleCommand(command: Command.RUN, cursor: number): boolean;
|
||||
handleCommand(command: Command, cursor: number, useMode?: boolean | MoveUseMode, move?: TurnMove): boolean;
|
||||
|
||||
public handleCommand(
|
||||
command: Command,
|
||||
cursor: number,
|
||||
useMode: boolean | MoveUseMode = false,
|
||||
move?: TurnMove,
|
||||
): boolean {
|
||||
let success = false;
|
||||
|
||||
switch (command) {
|
||||
// TODO: We don't need 2 args for this - moveUseMode is carried over from queuedMove
|
||||
case Command.TERA:
|
||||
case Command.FIGHT: {
|
||||
let useStruggle = false;
|
||||
const turnMove: TurnMove | undefined = args.length === 2 ? (args[1] as TurnMove) : undefined;
|
||||
if (
|
||||
cursor === -1 ||
|
||||
playerPokemon.trySelectMove(cursor, isIgnorePP(args[0] as MoveUseMode)) ||
|
||||
(useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m.isUsable(playerPokemon)).length)
|
||||
) {
|
||||
let moveId: MoveId;
|
||||
if (useStruggle) {
|
||||
moveId = MoveId.STRUGGLE;
|
||||
} else if (turnMove !== undefined) {
|
||||
moveId = turnMove.move;
|
||||
} else if (cursor > -1) {
|
||||
moveId = playerPokemon.getMoveset()[cursor].moveId;
|
||||
} else {
|
||||
moveId = MoveId.NONE;
|
||||
}
|
||||
|
||||
const turnCommand: TurnCommand = {
|
||||
command: Command.FIGHT,
|
||||
cursor: cursor,
|
||||
move: { move: moveId, targets: [], useMode: args[0] },
|
||||
args: args,
|
||||
};
|
||||
const preTurnCommand: TurnCommand = {
|
||||
command: command,
|
||||
targets: [this.fieldIndex],
|
||||
skip: command === Command.FIGHT,
|
||||
};
|
||||
const moveTargets: MoveTargetSet =
|
||||
turnMove === undefined
|
||||
? getMoveTargets(playerPokemon, moveId)
|
||||
: {
|
||||
targets: turnMove.targets,
|
||||
multiple: turnMove.targets.length > 1,
|
||||
};
|
||||
if (!moveId) {
|
||||
turnCommand.targets = [this.fieldIndex];
|
||||
}
|
||||
console.log(moveTargets, getPokemonNameWithAffix(playerPokemon));
|
||||
if (moveTargets.targets.length > 1 && moveTargets.multiple) {
|
||||
globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex);
|
||||
}
|
||||
if (turnCommand.move && (moveTargets.targets.length <= 1 || moveTargets.multiple)) {
|
||||
turnCommand.move.targets = moveTargets.targets;
|
||||
} else if (
|
||||
turnCommand.move &&
|
||||
playerPokemon.getTag(BattlerTagType.CHARGING) &&
|
||||
playerPokemon.getMoveQueue().length >= 1
|
||||
) {
|
||||
turnCommand.move.targets = playerPokemon.getMoveQueue()[0].targets;
|
||||
} else {
|
||||
globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex);
|
||||
}
|
||||
globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand;
|
||||
globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
|
||||
success = true;
|
||||
} else if (cursor < playerPokemon.getMoveset().length) {
|
||||
const move = playerPokemon.getMoveset()[cursor];
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
|
||||
// Decides between a Disabled, Not Implemented, or No PP translation message
|
||||
const errorMessage = playerPokemon.isMoveRestricted(move.moveId, playerPokemon)
|
||||
? playerPokemon
|
||||
.getRestrictingTag(move.moveId, playerPokemon)!
|
||||
.selectionDeniedText(playerPokemon, move.moveId)
|
||||
: move.getName().endsWith(" (N)")
|
||||
? "battle:moveNotImplemented"
|
||||
: "battle:moveNoPP";
|
||||
const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator
|
||||
|
||||
globalScene.ui.showText(
|
||||
i18next.t(errorMessage, { moveName: moveName }),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.clearText();
|
||||
globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex);
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
}
|
||||
case Command.FIGHT:
|
||||
success = this.handleFightCommand(command, cursor, typeof useMode === "boolean" ? undefined : useMode, move);
|
||||
break;
|
||||
}
|
||||
case Command.BALL: {
|
||||
const notInDex =
|
||||
globalScene
|
||||
.getEnemyField()
|
||||
.filter(p => p.isActive(true))
|
||||
.some(p => !globalScene.gameData.dexData[p.species.speciesId].caughtAttr) &&
|
||||
globalScene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1;
|
||||
if (
|
||||
globalScene.arena.biomeType === BiomeId.END &&
|
||||
(!globalScene.gameMode.isClassic || globalScene.gameMode.isFreshStartChallenge() || notInDex)
|
||||
) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:noPokeballForce"),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
} else if (globalScene.currentBattle.battleType === BattleType.TRAINER) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:noPokeballTrainer"),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
} else if (
|
||||
globalScene.currentBattle.isBattleMysteryEncounter() &&
|
||||
!globalScene.currentBattle.mysteryEncounter!.catchAllowed
|
||||
) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:noPokeballMysteryEncounter"),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
const targets = globalScene
|
||||
.getEnemyField()
|
||||
.filter(p => p.isActive(true))
|
||||
.map(p => p.getBattlerIndex());
|
||||
if (targets.length > 1) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:noPokeballMulti"),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
} else if (cursor < 5) {
|
||||
const targetPokemon = globalScene.getEnemyField().find(p => p.isActive(true));
|
||||
if (
|
||||
targetPokemon?.isBoss() &&
|
||||
targetPokemon?.bossSegmentIndex >= 1 &&
|
||||
!targetPokemon?.hasAbility(AbilityId.WONDER_GUARD, false, true) &&
|
||||
cursor < PokeballType.MASTER_BALL
|
||||
) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:noPokeballStrong"),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
globalScene.currentBattle.turnCommands[this.fieldIndex] = {
|
||||
command: Command.BALL,
|
||||
cursor: cursor,
|
||||
};
|
||||
globalScene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets;
|
||||
if (this.fieldIndex) {
|
||||
globalScene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
case Command.BALL:
|
||||
success = this.handleBallCommand(cursor);
|
||||
break;
|
||||
}
|
||||
case Command.POKEMON:
|
||||
case Command.RUN: {
|
||||
const isSwitch = command === Command.POKEMON;
|
||||
const { currentBattle, arena } = globalScene;
|
||||
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed;
|
||||
if (
|
||||
!isSwitch &&
|
||||
(arena.biomeType === BiomeId.END ||
|
||||
(!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))
|
||||
) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:noEscapeForce"),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
} else if (
|
||||
!isSwitch &&
|
||||
(currentBattle.battleType === BattleType.TRAINER ||
|
||||
currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)
|
||||
) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:noEscapeTrainer"),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
const batonPass = isSwitch && (args[0] as boolean);
|
||||
const trappedAbMessages: string[] = [];
|
||||
if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) {
|
||||
currentBattle.turnCommands[this.fieldIndex] = isSwitch
|
||||
? { command: Command.POKEMON, cursor: cursor, args: args }
|
||||
: { command: Command.RUN };
|
||||
success = true;
|
||||
if (!isSwitch && this.fieldIndex) {
|
||||
currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
|
||||
}
|
||||
} else if (trappedAbMessages.length > 0) {
|
||||
if (!isSwitch) {
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
}
|
||||
globalScene.ui.showText(
|
||||
trappedAbMessages[0],
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
if (!isSwitch) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
const trapTag = playerPokemon.getTag(TrappedTag);
|
||||
const fairyLockTag = globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER);
|
||||
|
||||
if (!trapTag && !fairyLockTag) {
|
||||
i18next.t(`battle:noEscape${isSwitch ? "Switch" : "Flee"}`);
|
||||
break;
|
||||
}
|
||||
if (!isSwitch) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||
}
|
||||
const showNoEscapeText = (tag: any) => {
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:noEscapePokemon", {
|
||||
pokemonName:
|
||||
tag.sourceId && globalScene.getPokemonById(tag.sourceId)
|
||||
? getPokemonNameWithAffix(globalScene.getPokemonById(tag.sourceId)!)
|
||||
: "",
|
||||
moveName: tag.getMoveName(),
|
||||
escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee"),
|
||||
}),
|
||||
null,
|
||||
() => {
|
||||
globalScene.ui.showText("", 0);
|
||||
if (!isSwitch) {
|
||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||
}
|
||||
},
|
||||
null,
|
||||
true,
|
||||
);
|
||||
};
|
||||
|
||||
if (trapTag) {
|
||||
showNoEscapeText(trapTag);
|
||||
} else if (fairyLockTag) {
|
||||
showNoEscapeText(fairyLockTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.isSwitch = true;
|
||||
success = this.tryLeaveField(cursor, typeof useMode === "boolean" ? useMode : undefined);
|
||||
this.isSwitch = false;
|
||||
break;
|
||||
}
|
||||
case Command.RUN:
|
||||
success = this.handleRunCommand();
|
||||
}
|
||||
|
||||
if (success) {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { Phase } from "#app/phase";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { addTextObject, TextStyle } from "#ui/text";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
import { addTextObject } from "#ui/text";
|
||||
import i18next from "i18next";
|
||||
|
||||
export class EndCardPhase extends Phase {
|
||||
|
@ -135,7 +135,7 @@ export class EvolutionPhase extends Phase {
|
||||
|
||||
sprite
|
||||
.setPipelineData("ignoreTimeTint", true)
|
||||
.setPipelineData("spriteKey", pokemon.getSpriteKey())
|
||||
.setPipelineData("spriteKey", spriteKey)
|
||||
.setPipelineData("shiny", pokemon.shiny)
|
||||
.setPipelineData("variant", pokemon.variant);
|
||||
|
||||
|
@ -13,12 +13,12 @@ import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { HitCheckResult } from "#enums/hit-check-result";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
|
||||
import { MoveFlags } from "#enums/MoveFlags";
|
||||
import { MoveTarget } from "#enums/MoveTarget";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveEffectTrigger } from "#enums/move-effect-trigger";
|
||||
import { MoveFlags } from "#enums/move-flags";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
import { MoveTarget } from "#enums/move-target";
|
||||
import { isReflected, isVirtual, MoveUseMode } from "#enums/move-use-mode";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
|
@ -13,8 +13,8 @@ import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { MoveFlags } from "#enums/MoveFlags";
|
||||
import { CommonAnim } from "#enums/move-anims-common";
|
||||
import { MoveFlags } from "#enums/move-flags";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
import { isIgnorePP, isIgnoreStatus, isReflected, isVirtual, MoveUseMode } from "#enums/move-use-mode";
|
||||
|
@ -2,9 +2,10 @@ import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { PERMANENT_STATS, Stat } from "#enums/stat";
|
||||
import { TextStyle } from "#enums/text-style";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { PokemonPhase } from "#phases/pokemon-phase";
|
||||
import { getTextColor, TextStyle } from "#ui/text";
|
||||
import { getTextColor } from "#ui/text";
|
||||
import i18next from "i18next";
|
||||
|
||||
export class ScanIvsPhase extends PokemonPhase {
|
||||
|
@ -2,8 +2,8 @@ import { globalScene } from "#app/global-scene";
|
||||
import { getTerrainColor, TerrainType } from "#data/terrain";
|
||||
import { getCurrentTime } from "#utils/common";
|
||||
import Phaser from "phaser";
|
||||
import fieldSpriteFragShader from "./glsl/fieldSpriteFragShader.frag?raw";
|
||||
import spriteVertShader from "./glsl/spriteShader.vert?raw";
|
||||
import fieldSpriteFragShader from "./glsl/field-sprite-frag-shader.frag?raw";
|
||||
import spriteVertShader from "./glsl/sprite-shader.vert?raw";
|
||||
|
||||
export class FieldSpritePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPipeline {
|
||||
constructor(game: Phaser.Game, config?: Phaser.Types.Renderer.WebGL.WebGLPipelineConfig) {
|
||||
|
@ -5,8 +5,8 @@ import { Pokemon } from "#field/pokemon";
|
||||
import { Trainer } from "#field/trainer";
|
||||
import { variantColorCache } from "#sprites/variant";
|
||||
import { rgbHexToRgba } from "#utils/common";
|
||||
import spriteFragShader from "./glsl/spriteFragShader.frag?raw";
|
||||
import spriteVertShader from "./glsl/spriteShader.vert?raw";
|
||||
import spriteFragShader from "./glsl/sprite-frag-shader.frag?raw";
|
||||
import spriteVertShader from "./glsl/sprite-shader.vert?raw";
|
||||
|
||||
export class SpritePipeline extends FieldSpritePipeline {
|
||||
private _tone: number[];
|
||||
|
@ -5,7 +5,7 @@ import type {
|
||||
AccountLoginRequest,
|
||||
AccountLoginResponse,
|
||||
AccountRegisterRequest,
|
||||
} from "#types/PokerogueAccountApi";
|
||||
} from "#types/api/pokerogue-account-api";
|
||||
import { removeCookie, setCookie } from "#utils/cookies";
|
||||
|
||||
/**
|
||||
|
@ -6,7 +6,7 @@ import type {
|
||||
SearchAccountResponse,
|
||||
UnlinkAccountFromDiscordIdRequest,
|
||||
UnlinkAccountFromGoogledIdRequest,
|
||||
} from "#types/PokerogueAdminApi";
|
||||
} from "#types/api/pokerogue-admin-api";
|
||||
|
||||
export class PokerogueAdminApi extends ApiBase {
|
||||
public readonly ERR_USERNAME_NOT_FOUND: string = "Username not found!";
|
||||
|
@ -3,7 +3,7 @@ import { PokerogueAccountApi } from "#api/pokerogue-account-api";
|
||||
import { PokerogueAdminApi } from "#api/pokerogue-admin-api";
|
||||
import { PokerogueDailyApi } from "#api/pokerogue-daily-api";
|
||||
import { PokerogueSavedataApi } from "#api/pokerogue-savedata-api";
|
||||
import type { TitleStatsResponse } from "#types/PokerogueApi";
|
||||
import type { TitleStatsResponse } from "#types/api/pokerogue-api-types";
|
||||
|
||||
/**
|
||||
* A wrapper for PokéRogue API requests.
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ApiBase } from "#api/api-base";
|
||||
import type { GetDailyRankingsPageCountRequest, GetDailyRankingsRequest } from "#types/PokerogueDailyApi";
|
||||
import type { GetDailyRankingsPageCountRequest, GetDailyRankingsRequest } from "#types/api/pokerogue-daily-api";
|
||||
import type { RankingEntry } from "#ui/daily-run-scoreboard";
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,7 @@ import { ApiBase } from "#api/api-base";
|
||||
import { PokerogueSessionSavedataApi } from "#api/pokerogue-session-savedata-api";
|
||||
import { PokerogueSystemSavedataApi } from "#api/pokerogue-system-savedata-api";
|
||||
import { MAX_INT_ATTR_VALUE } from "#app/constants";
|
||||
import type { UpdateAllSavedataRequest } from "#types/PokerogueSavedataApi";
|
||||
import type { UpdateAllSavedataRequest } from "#types/api/pokerogue-save-data-api";
|
||||
|
||||
/**
|
||||
* A wrapper for PokéRogue savedata API requests.
|
||||
|
@ -7,7 +7,7 @@ import type {
|
||||
GetSessionSavedataRequest,
|
||||
NewClearSessionSavedataRequest,
|
||||
UpdateSessionSavedataRequest,
|
||||
} from "#types/PokerogueSessionSavedataApi";
|
||||
} from "#types/api/pokerogue-session-save-data-api";
|
||||
|
||||
/**
|
||||
* A wrapper for PokéRogue session savedata API requests.
|
||||
|
@ -4,7 +4,7 @@ import type {
|
||||
UpdateSystemSavedataRequest,
|
||||
VerifySystemSavedataRequest,
|
||||
VerifySystemSavedataResponse,
|
||||
} from "#types/PokerogueSystemSavedataApi";
|
||||
} from "#types/api/pokerogue-system-save-data-api";
|
||||
|
||||
/**
|
||||
* A wrapper for PokéRogue system savedata API requests.
|
||||
|
@ -1,5 +1,5 @@
|
||||
import pkg from "#package.json";
|
||||
import { camelCaseToKebabCase } from "#utils/common";
|
||||
import { toKebabCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import HttpBackend from "i18next-http-backend";
|
||||
@ -194,14 +194,16 @@ export async function initI18n(): Promise<void> {
|
||||
],
|
||||
backend: {
|
||||
loadPath(lng: string, [ns]: string[]) {
|
||||
// Use namespace maps where required
|
||||
let fileName: string;
|
||||
if (namespaceMap[ns]) {
|
||||
fileName = namespaceMap[ns];
|
||||
} else if (ns.startsWith("mysteryEncounters/")) {
|
||||
fileName = camelCaseToKebabCase(ns + "Dialogue");
|
||||
fileName = toKebabCase(ns + "-dialogue"); // mystery-encounters/a-trainers-test-dialogue
|
||||
} else {
|
||||
fileName = camelCaseToKebabCase(ns);
|
||||
fileName = toKebabCase(ns);
|
||||
}
|
||||
// ex: "./locales/en/move-anims"
|
||||
return `./locales/${lng}/${fileName}.json?v=${pkg.version}`;
|
||||
},
|
||||
},
|
||||
|
@ -890,7 +890,7 @@ export const achvs = {
|
||||
100,
|
||||
c =>
|
||||
c instanceof FreshStartChallenge &&
|
||||
c.value > 0 &&
|
||||
c.value === 1 &&
|
||||
!globalScene.gameMode.challenges.some(
|
||||
c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0,
|
||||
),
|
||||
|
@ -57,7 +57,7 @@ import {
|
||||
applySessionVersionMigration,
|
||||
applySettingsVersionMigration,
|
||||
applySystemVersionMigration,
|
||||
} from "#system/version_converter";
|
||||
} from "#system/version-migration/version-converter";
|
||||
import { VoucherType, vouchers } from "#system/voucher";
|
||||
import { trainerConfigs } from "#trainers/trainer-config";
|
||||
import type { DexData, DexEntry } from "#types/dex-data";
|
||||
@ -1454,11 +1454,10 @@ export class GameData {
|
||||
|
||||
reader.onload = (_ => {
|
||||
return e => {
|
||||
let dataName: string;
|
||||
let dataName = GameDataType[dataType].toLowerCase();
|
||||
let dataStr = AES.decrypt(e.target?.result?.toString()!, saveKey).toString(enc.Utf8); // TODO: is this bang correct?
|
||||
let valid = false;
|
||||
try {
|
||||
dataName = GameDataType[dataType].toLowerCase();
|
||||
switch (dataType) {
|
||||
case GameDataType.SYSTEM: {
|
||||
dataStr = this.convertSystemDataStr(dataStr);
|
||||
@ -1493,7 +1492,6 @@ export class GameData {
|
||||
|
||||
const displayError = (error: string) =>
|
||||
globalScene.ui.showText(error, null, () => globalScene.ui.showText("", 0), fixedInt(1500));
|
||||
dataName = dataName!; // tell TS compiler that dataName is defined!
|
||||
|
||||
if (!valid) {
|
||||
return globalScene.ui.showText(
|
||||
|
@ -88,12 +88,12 @@ export class PokemonData {
|
||||
this.id = source.id;
|
||||
this.player = sourcePokemon?.isPlayer() ?? source.player;
|
||||
this.species = sourcePokemon?.species.speciesId ?? source.species;
|
||||
this.nickname = sourcePokemon?.summonData.illusion?.basePokemon.nickname ?? source.nickname;
|
||||
this.nickname = source.nickname;
|
||||
this.formIndex = Math.max(Math.min(source.formIndex, getPokemonSpecies(this.species).forms.length - 1), 0);
|
||||
this.abilityIndex = source.abilityIndex;
|
||||
this.passive = source.passive;
|
||||
this.shiny = sourcePokemon?.summonData.illusion?.basePokemon.shiny ?? source.shiny;
|
||||
this.variant = sourcePokemon?.summonData.illusion?.basePokemon.variant ?? source.variant;
|
||||
this.shiny = source.shiny;
|
||||
this.variant = source.variant;
|
||||
this.pokeball = source.pokeball ?? PokeballType.POKEBALL;
|
||||
this.level = source.level;
|
||||
this.exp = source.exp;
|
||||
@ -134,8 +134,8 @@ export class PokemonData {
|
||||
this.fusionSpecies = sourcePokemon?.fusionSpecies?.speciesId ?? source.fusionSpecies;
|
||||
this.fusionFormIndex = source.fusionFormIndex;
|
||||
this.fusionAbilityIndex = source.fusionAbilityIndex;
|
||||
this.fusionShiny = sourcePokemon?.summonData.illusion?.basePokemon.fusionShiny ?? source.fusionShiny;
|
||||
this.fusionVariant = sourcePokemon?.summonData.illusion?.basePokemon.fusionVariant ?? source.fusionVariant;
|
||||
this.fusionShiny = source.fusionShiny;
|
||||
this.fusionVariant = source.fusionVariant;
|
||||
this.fusionGender = source.fusionGender;
|
||||
this.fusionLuck = source.fusionLuck ?? (source.fusionShiny ? source.fusionVariant + 1 : 0);
|
||||
this.fusionTeraType = (source.fusionTeraType ?? 0) as PokemonType;
|
||||
|
@ -171,6 +171,7 @@ export const SettingKeys = {
|
||||
UI_Volume: "UI_SOUND_EFFECTS",
|
||||
Battle_Music: "BATTLE_MUSIC",
|
||||
Show_BGM_Bar: "SHOW_BGM_BAR",
|
||||
Hide_Username: "HIDE_USERNAME",
|
||||
Move_Touch_Controls: "MOVE_TOUCH_CONTROLS",
|
||||
Shop_Overlay_Opacity: "SHOP_OVERLAY_OPACITY",
|
||||
};
|
||||
@ -625,6 +626,13 @@ export const Setting: Array<Setting> = [
|
||||
default: 1,
|
||||
type: SettingType.DISPLAY,
|
||||
},
|
||||
{
|
||||
key: SettingKeys.Hide_Username,
|
||||
label: i18next.t("settings:hideUsername"),
|
||||
options: OFF_ON,
|
||||
default: 0,
|
||||
type: SettingType.DISPLAY,
|
||||
},
|
||||
{
|
||||
key: SettingKeys.Master_Volume,
|
||||
label: i18next.t("settings:masterVolume"),
|
||||
@ -792,6 +800,9 @@ export function setSetting(setting: string, value: number): boolean {
|
||||
case SettingKeys.Show_BGM_Bar:
|
||||
globalScene.showBgmBar = Setting[index].options[value].value === "On";
|
||||
break;
|
||||
case SettingKeys.Hide_Username:
|
||||
globalScene.hideUsername = Setting[index].options[value].value === "On";
|
||||
break;
|
||||
case SettingKeys.Candy_Upgrade_Notification:
|
||||
if (globalScene.candyUpgradeNotification === value) {
|
||||
break;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user