Merge branch 'beta' into SplitPassiveTest

This commit is contained in:
damocleas 2025-03-25 23:16:08 -04:00 committed by GitHub
commit 0a809b419d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
728 changed files with 64367 additions and 45620 deletions

View File

@ -2,92 +2,86 @@
module.exports = {
forbidden: [
{
name: 'no-circular-at-runtime',
severity: 'warn',
name: "no-circular-at-runtime",
severity: "warn",
comment:
'This dependency is part of a circular relationship. You might want to revise ' +
'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ',
"This dependency is part of a circular relationship. You might want to revise " +
"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ",
from: {},
to: {
circular: true,
viaOnly: {
dependencyTypesNot: [
'type-only'
]
}
}
dependencyTypesNot: ["type-only"],
},
},
},
{
name: 'no-orphans',
name: "no-orphans",
comment:
"This is an orphan module - it's likely not used (anymore?). Either use it or " +
"remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
"add an exception for it in your dependency-cruiser configuration. By default " +
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
severity: 'warn',
severity: "warn",
from: {
orphan: true,
pathNot: [
'(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files
'[.]d[.]ts$', // TypeScript declaration files
'(^|/)tsconfig[.]json$', // TypeScript config
'(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs
]
"(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$", // dot files
"[.]d[.]ts$", // TypeScript declaration files
"(^|/)tsconfig[.]json$", // TypeScript config
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs
],
},
to: {},
},
{
name: 'no-deprecated-core',
name: "no-deprecated-core",
comment:
'A module depends on a node core module that has been deprecated. Find an alternative - these are ' +
"A module depends on a node core module that has been deprecated. Find an alternative - these are " +
"bound to exist - node doesn't deprecate lightly.",
severity: 'warn',
severity: "warn",
from: {},
to: {
dependencyTypes: [
'core'
],
dependencyTypes: ["core"],
path: [
'^v8/tools/codemap$',
'^v8/tools/consarray$',
'^v8/tools/csvparser$',
'^v8/tools/logreader$',
'^v8/tools/profile_view$',
'^v8/tools/profile$',
'^v8/tools/SourceMap$',
'^v8/tools/splaytree$',
'^v8/tools/tickprocessor-driver$',
'^v8/tools/tickprocessor$',
'^node-inspect/lib/_inspect$',
'^node-inspect/lib/internal/inspect_client$',
'^node-inspect/lib/internal/inspect_repl$',
'^async_hooks$',
'^punycode$',
'^domain$',
'^constants$',
'^sys$',
'^_linklist$',
'^_stream_wrap$'
"^v8/tools/codemap$",
"^v8/tools/consarray$",
"^v8/tools/csvparser$",
"^v8/tools/logreader$",
"^v8/tools/profile_view$",
"^v8/tools/profile$",
"^v8/tools/SourceMap$",
"^v8/tools/splaytree$",
"^v8/tools/tickprocessor-driver$",
"^v8/tools/tickprocessor$",
"^node-inspect/lib/_inspect$",
"^node-inspect/lib/internal/inspect_client$",
"^node-inspect/lib/internal/inspect_repl$",
"^async_hooks$",
"^punycode$",
"^domain$",
"^constants$",
"^sys$",
"^_linklist$",
"^_stream_wrap$",
],
}
},
},
{
name: 'not-to-deprecated',
name: "not-to-deprecated",
comment:
'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' +
'version of that module, or find an alternative. Deprecated modules are a security risk.',
severity: 'warn',
"This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " +
"version of that module, or find an alternative. Deprecated modules are a security risk.",
severity: "warn",
from: {},
to: {
dependencyTypes: [
'deprecated'
]
}
dependencyTypes: ["deprecated"],
},
},
{
name: 'no-non-package-json',
severity: 'error',
name: "no-non-package-json",
severity: "error",
comment:
"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
"That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
@ -95,87 +89,75 @@ module.exports = {
"in your package.json.",
from: {},
to: {
dependencyTypes: [
'npm-no-pkg',
'npm-unknown'
]
}
dependencyTypes: ["npm-no-pkg", "npm-unknown"],
},
},
{
name: 'not-to-unresolvable',
name: "not-to-unresolvable",
comment:
"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
'module: add it to your package.json. In all other cases you likely already know what to do.',
severity: 'error',
"module: add it to your package.json. In all other cases you likely already know what to do.",
severity: "error",
from: {},
to: {
couldNotResolve: true
}
couldNotResolve: true,
},
},
{
name: 'no-duplicate-dep-types',
name: "no-duplicate-dep-types",
comment:
"Likely this module depends on an external ('npm') package that occurs more than once " +
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
"maintenance problems later on.",
severity: 'warn',
severity: "warn",
from: {},
to: {
moreThanOneDependencyType: true,
// as it's pretty common to have a type import be a type only import
// _and_ (e.g.) a devDependency - don't consider type-only dependency
// types for this rule
dependencyTypesNot: ["type-only"]
}
dependencyTypesNot: ["type-only"],
},
},
/* rules you might want to tweak for your specific situation: */
{
name: 'not-to-spec',
name: "not-to-spec",
comment:
'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' +
"This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. " +
"If there's something in a spec that's of use to other modules, it doesn't have that single " +
'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.',
severity: 'error',
"responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.",
severity: "error",
from: {},
to: {
path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
}
path: "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$",
},
},
{
name: 'not-to-dev-dep',
severity: 'error',
name: "not-to-dev-dep",
severity: "error",
comment:
"This module depends on an npm package from the 'devDependencies' section of your " +
'package.json. It looks like something that ships to production, though. To prevent problems ' +
"package.json. It looks like something that ships to production, though. To prevent problems " +
"with npm packages that aren't there on production declare it (only!) in the 'dependencies'" +
'section of your package.json. If this module is development only - add it to the ' +
'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration',
"section of your package.json. If this module is development only - add it to the " +
"from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration",
from: {
path: '^(src)',
pathNot: [
'[.](?:spec|test|setup|script)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$',
'./test'
]
path: "^(src)",
pathNot: ["[.](?:spec|test|setup|script)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$", "./test"],
},
to: {
dependencyTypes: [
'npm-dev',
],
dependencyTypes: ["npm-dev"],
// type only dependencies are not a problem as they don't end up in the
// production code or are ignored by the runtime.
dependencyTypesNot: [
'type-only'
],
pathNot: [
'node_modules/@types/'
]
}
dependencyTypesNot: ["type-only"],
pathNot: ["node_modules/@types/"],
},
},
{
name: 'optional-deps-used',
severity: 'info',
name: "optional-deps-used",
severity: "info",
comment:
"This module depends on an npm package that is declared as an optional dependency " +
"in your package.json. As this makes sense in limited situations only, it's flagged here. " +
@ -183,33 +165,28 @@ module.exports = {
"dependency-cruiser configuration.",
from: {},
to: {
dependencyTypes: [
'npm-optional'
]
}
dependencyTypes: ["npm-optional"],
},
},
{
name: 'peer-deps-used',
name: "peer-deps-used",
comment:
"This module depends on an npm package that is declared as a peer dependency " +
"in your package.json. This makes sense if your package is e.g. a plugin, but in " +
"other cases - maybe not so much. If the use of a peer dependency is intentional " +
"add an exception to your dependency-cruiser configuration.",
severity: 'warn',
severity: "warn",
from: {},
to: {
dependencyTypes: [
'npm-peer'
]
}
}
dependencyTypes: ["npm-peer"],
},
},
],
options: {
/* Which modules not to follow further when encountered */
doNotFollow: {
/* path: an array of regular expressions in strings to match against */
path: ['node_modules']
path: ["node_modules"],
},
/* Which modules to exclude */
@ -271,7 +248,7 @@ module.exports = {
defaults to './tsconfig.json'.
*/
tsConfig: {
fileName: 'tsconfig.json'
fileName: "tsconfig.json",
},
/* Webpack configuration to use to get resolve options from.
@ -345,7 +322,7 @@ module.exports = {
collapses everything in node_modules to one folder deep so you see
the external modules, but their innards.
*/
collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)',
collapsePattern: "node_modules/(?:@[^/]+/[^/]+|[^/]+)",
/* Options to tweak the appearance of your graph.See
https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions
@ -367,7 +344,8 @@ module.exports = {
dependency graph reporter (`archi`) you probably want to tweak
this collapsePattern to your situation.
*/
collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)',
collapsePattern:
"^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)",
/* Options to tweak the appearance of your graph. If you don't specify a
theme for 'archi' dependency-cruiser will use the one specified in the
@ -375,10 +353,10 @@ module.exports = {
*/
// theme: { },
},
"text": {
"highlightFocused": true
text: {
highlightFocused: true,
},
},
},
}
}
};
// generated: dependency-cruiser@16.3.3 on 2024-06-13T23:26:36.169Z

1
.gitattributes vendored
View File

@ -1,2 +1,3 @@
# Auto detect text files and perform LF normalization
* text=auto
* -crlf

View File

@ -1,6 +1,7 @@
name: Bug Report
description: Create a report to help us improve
title: "[Bug] "
type: bug
labels: ["Bug", "Triage"]
body:
- type: markdown

View File

@ -1,6 +1,7 @@
name: Feature Request
description: Suggest an idea for this project
title: "[Feature] "
type: 'feature'
labels: ["Enhancement", "Triage"]
body:
- type: markdown

View File

@ -1,4 +1,4 @@
name: ESLint
name: Biome Code Quality
on:
# Trigger the workflow on push or pull request,
@ -28,10 +28,13 @@ jobs:
- name: Set up Node.js # Step to set up Node.js environment
uses: actions/setup-node@v4 # Use the setup-node action version 4
with:
node-version: 20 # Specify Node.js version 20
node-version-file: '.nvmrc'
- name: Install Node.js dependencies # Step to install Node.js dependencies
run: npm ci # Use 'npm ci' to install dependencies
- name: eslint # Step to run linters
run: npm run eslint-ci
- name: Lint with Biome # Step to run linters
run: npm run biome-ci

View File

@ -364,11 +364,13 @@ In addition to the lists below, please check [the PokéRogue wiki](https://wiki.
- Opaquer
- OrangeRed
- Sam aka Flashfyre (initial developer, started PokéRogue)
- SirzBenjie
- sirzento
- SN34KZ
- Swain aka torranx
- Temp aka Tempo-anon
- Walker
- Wlowscha (aka Curbio)
- Xavion
## Bug/Issue Managers

View File

@ -3,23 +3,30 @@
PokéRogue is a browser based Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, fighting trainers, bosses, and more!
# Contributing
## 🛠️ Development
If you have the motivation and experience with Typescript/Javascript (or are willing to learn) please feel free to fork the repository and make pull requests with contributions. If you don't know what to work on but want to help, reference the below **To-Do** section or the **#feature-vote** channel in the discord.
### 💻 Environment Setup
#### Prerequisites
- node: 20.13.1
- npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
#### Running Locally
1. Clone the repo and in the root directory run `npm install`
- *if you run into any errors, reach out in the **#dev-corner** channel in discord*
2. Run `npm run start:dev` to locally run the project in `localhost:8000`
#### Linting
We're using ESLint as our common linter and formatter. It will run automatically during the pre-commit hook but if you would like to manually run it, use the `npm run eslint` script. To view the complete rules, check out the [eslint.config.js](./eslint.config.js) file.
We're using Biome as our common linter and formatter. It will run automatically during the pre-commit hook but if you would like to manually run it, use the `npm run biome` script. To view the complete rules, check out the [biome.jsonc](./biome.jsonc) file.
### 📚 Documentation
You can find the auto-generated documentation [here](https://pagefaultgames.github.io/pokerogue/main/index.html).
For information on enemy AI, check out the [enemy-ai.md](./docs/enemy-ai.md) file.
For detailed guidelines on documenting your code, refer to the [comments.md](./docs/comments.md) file.
@ -27,16 +34,20 @@ For detailed guidelines on documenting your code, refer to the [comments.md](./d
### ❔ FAQ
**How do I test a new _______?**
- In the `src/overrides.ts` file there are overrides for most values you'll need to change for testing
**How do I retrieve the translations?**
- The translations were moved to the [dedicated translation repository](https://github.com/pagefaultgames/pokerogue-locales) and are now applied as a submodule in this project.
- The command to retrieve the translations is `git submodule update --init --recursive`. If you still struggle to get it working, please reach out to #dev-corner channel in Discord.
## 🪧 To Do
Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us!
# 📝 Credits
>
> If this project contains assets you have produced and you do not see your name, **please** reach out, either [here on GitHub](https://github.com/pagefaultgames/pokerogue/issues/new) or via [Discord](https://discord.gg/pokerogue).
Thank you to all the wonderful people that have contributed to the PokéRogue project! You can find the credits [here](./CREDITS.md).

106
biome.jsonc Normal file
View File

@ -0,0 +1,106 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "beta"
},
"formatter": {
"enabled": true,
"useEditorconfig": true,
"indentStyle": "space",
"ignore": ["src/enums/*", "src/data/balance/*"],
"lineWidth": 120
},
"files": {
"ignoreUnknown": true,
// Adding folders to the ignore list is GREAT for performance because it prevents biome from descending into them
// and having to verify whether each individual file is ignored
"ignore": [
"**/*.d.ts",
"dist/*",
"build/*",
"coverage/*",
"public/*",
".github/*",
"node_modules/*",
".vscode/*",
"*.css", // TODO?
"*.html", // TODO?
"src/overrides.ts",
// TODO: these files are too big and complex, ignore them until their respective refactors
"src/data/moves/move.ts",
"src/data/ability.ts",
"src/field/pokemon.ts",
// this file is just too big:
"src/data/balance/tms.ts"
]
},
"organizeImports": { "enabled": false },
"linter": {
"ignore": [
"src/phases/move-effect-phase.ts" // TODO: unignore after move-effect-phase refactor
],
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUndeclaredVariables": "off",
"noUnusedVariables": "error",
"noSwitchDeclarations": "warn", // TODO: refactor and make this an error
"noVoidTypeReturn": "warn" // TODO: Refactor and make this an error
},
"style": {
"noVar": "error",
"useEnumInitializers": "off",
"useBlockStatements": "error",
"useConst": "error",
"useImportType": "error",
"noNonNullAssertion": "off", // TODO: Turn this on ASAP and fix all non-null assertions
"noParameterAssign": "off",
"useExponentiationOperator": "off",
"useDefaultParameterLast": "off", // TODO: Fix spots in the codebase where this flag would be triggered, and then enable
"useSingleVarDeclarator": "off",
"useNodejsImportProtocol": "off",
"useTemplate": "off" // string concatenation is faster: https://stackoverflow.com/questions/29055518/are-es6-template-literals-faster-than-string-concatenation
},
"suspicious": {
"noDoubleEquals": "error",
"noExplicitAny": "off",
"noAssignInExpressions": "off",
"noPrototypeBuiltins": "off",
"noFallthroughSwitchClause": "off",
"noImplicitAnyLet": "info", // TODO: Refactor and make this an error
"noRedeclare": "off", // TODO: Refactor and make this an error
"noGlobalIsNan": "off",
"noAsyncPromiseExecutor": "warn" // TODO: Refactor and make this an error
},
"complexity": {
"noExcessiveCognitiveComplexity": "warn",
"useLiteralKeys": "off",
"noForEach": "off", // Foreach vs for of is not that simple.
"noUselessSwitchCase": "off", // Explicit > Implicit
"noUselessConstructor": "warn", // TODO: Refactor and make this an error
"noBannedTypes": "warn" // TODO: Refactor and make this an error
}
}
},
"javascript": {
"formatter": { "quoteStyle": "double", "arrowParentheses": "asNeeded" }
},
"overrides": [
{
"include": ["test/**/*.test.ts"],
"javascript": { "globals": [] },
"linter": {
"rules": {
"performance": {
"noDelete": "off"
}
}
}
}
]
}

View File

@ -31,7 +31,8 @@ async function promptTestType() {
if (typeAnswer.selectedOption === "EXIT") {
console.log("Exiting...");
return process.exit();
} else if (!typeChoices.includes(typeAnswer.selectedOption)) {
}
if (!typeChoices.includes(typeAnswer.selectedOption)) {
console.error(`Please provide a valid type (${typeChoices.join(", ")})!`);
return await promptTestType();
}
@ -74,11 +75,11 @@ async function runInteractive() {
const fileName = fileNameAnswer.userInput
.replace(/-+/g, "_") // Convert kebab-case (dashes) to underscores
.replace(/([a-z])([A-Z])/g, "$1_$2") // Convert camelCase to snake_case
.replace(/\s+/g, '_') // Replace spaces with underscores
.replace(/\s+/g, "_") // Replace spaces with underscores
.toLowerCase(); // Ensure all lowercase
// Format the description for the test case
const formattedName = fileName.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
const formattedName = fileName.replace(/_/g, " ").replace(/\b\w/g, char => char.toUpperCase());
// Determine the directory based on the type
let dir;
let description;

View File

@ -1,7 +1,7 @@
import tseslint from '@typescript-eslint/eslint-plugin';
import stylisticTs from '@stylistic/eslint-plugin-ts';
import parser from '@typescript-eslint/parser';
import importX from 'eslint-plugin-import-x';
import tseslint from "@typescript-eslint/eslint-plugin";
import stylisticTs from "@stylistic/eslint-plugin-ts";
import parser from "@typescript-eslint/parser";
import importX from "eslint-plugin-import-x";
export default [
{
@ -9,46 +9,19 @@ export default [
files: ["src/**/*.{ts,tsx,js,jsx}", "test/**/*.{ts,tsx,js,jsx}"],
ignores: ["dist/*", "build/*", "coverage/*", "public/*", ".github/*", "node_modules/*", ".vscode/*"],
languageOptions: {
parser: parser
parser: parser,
},
plugins: {
"import-x": importX,
'@stylistic/ts': stylisticTs,
'@typescript-eslint': tseslint
"@stylistic/ts": stylisticTs,
"@typescript-eslint": tseslint,
},
rules: {
"eqeqeq": ["error", "always"], // Enforces the use of `===` and `!==` instead of `==` and `!=`
"indent": ["error", 2, { "SwitchCase": 1 }], // Enforces a 2-space indentation, enforces indentation of `case ...:` statements
"quotes": ["error", "double"], // Enforces the use of double quotes for strings
"no-var": "error", // Disallows the use of `var`, enforcing `let` or `const` instead
"prefer-const": "error", // Enforces the use of `const` for variables that are never reassigned
"no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this)
"@typescript-eslint/no-unused-vars": [ "error", {
"args": "none", // Allows unused function parameters. Useful for functions with specific signatures where not all parameters are always used.
"ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the others.
}],
"eol-last": ["error", "always"], // Enforces at least one newline at the end of files
"@stylistic/ts/semi": ["error", "always"], // Requires semicolons for TypeScript-specific syntax
"semi": "off", // Disables the general semi rule for TypeScript files
"no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax
"brace-style": "off", // Note: you must disable the base rule as it can report incorrect errors
"curly": ["error", "all"], // Enforces the use of curly braces for all control statements
"@stylistic/ts/brace-style": ["error", "1tbs"], // Enforces the following brace style: https://eslint.style/rules/js/brace-style#_1tbs
"no-trailing-spaces": ["error", { // Disallows trailing whitespace at the end of lines
"skipBlankLines": false, // Enforces the rule even on blank lines
"ignoreComments": false // Enforces the rule on lines containing comments
}],
"space-before-blocks": ["error", "always"], // Enforces a space before blocks
"keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords
"comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after commas
"import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json
"array-bracket-spacing": ["error", "always", { "objectsInArrays": false, "arraysInArrays": false }], // Enforces consistent spacing inside array brackets
"object-curly-spacing": ["error", "always", { "arraysInObjects": false, "objectsInObjects": false }], // Enforces consistent spacing inside braces of object literals, destructuring assignments, and import/export specifiers
"computed-property-spacing": ["error", "never" ], // Enforces consistent spacing inside computed property brackets
"space-infix-ops": ["error", { "int32Hint": false }], // Enforces spacing around infix operators
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], // Disallows multiple empty lines
"@typescript-eslint/consistent-type-imports": "error", // Enforces type-only imports wherever possible
}
"import-x/extensions": ["error", "never", { json: "always" }], // Enforces no extension for imports unless json
},
},
{
name: "eslint-tests",
@ -56,15 +29,15 @@ export default [
languageOptions: {
parser: parser,
parserOptions: {
"project": ["./tsconfig.json"]
}
project: ["./tsconfig.json"],
},
},
plugins: {
"@typescript-eslint": tseslint
"@typescript-eslint": tseslint,
},
rules: {
"@typescript-eslint/no-floating-promises": "error", // Require Promise-like statements to be handled appropriately. - https://typescript-eslint.io/rules/no-floating-promises/
"@typescript-eslint/no-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/
}
}
]
},
},
];

110
index.css
View File

@ -49,16 +49,17 @@ body {
@media (pointer: coarse) {
/* hasTouchscreen() && !isTouchControlsEnabled */
body:has(> #touchControls[class=visible]) #app {
body:has(> #touchControls[class="visible"]) #app {
align-items: start;
}
body:has(> #touchControls[class=visible]) #app > div:first-child {
body:has(> #touchControls[class="visible"]) #app > div:first-child {
transform-origin: top !important;
}
}
#layout:fullscreen #dpad, #layout:fullscreen {
#layout:fullscreen #dpad,
#layout:fullscreen {
bottom: 6rem;
}
@ -67,6 +68,10 @@ input:-internal-autofill-selected {
background-clip: text;
}
input:-webkit-autofill {
-webkit-text-fill-color: #a1a1a1;
}
/* Need adjust input font-size */
input {
font-size: 3.2rem;
@ -76,7 +81,6 @@ input {
display: none !important;
}
input:-internal-autofill-selected {
-webkit-background-clip: text;
background-clip: text;
@ -91,18 +95,33 @@ input:-internal-autofill-selected {
--controls-padding: 1rem;
--controls-size-with-padding: calc(var(--controls-size) + var(--controls-padding));
--controls-size-with-wide-padding: calc(var(--controls-size) *1.2 + var(--controls-padding));
--controls-size-with-padding: calc(
var(--controls-size) +
var(--controls-padding)
);
--controls-size-with-wide-padding: calc(
var(--controls-size) *
1.2 +
var(--controls-padding)
);
--control-group-extra-size: calc(var(--controls-size) * 0.8);
--control-group-extra-wide-size: calc(var(--controls-size) * 1.2);
--control-group-extra-2-offset: calc(var(--controls-size-with-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2);
--control-group-extra-1-offset: calc(var(--controls-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2);
--control-group-extra-2-offset: calc(
var(--controls-size-with-padding) +
(var(--controls-size) - var(--control-group-extra-size)) /
2
);
--control-group-extra-1-offset: calc(
var(--controls-padding) +
(var(--controls-size) - var(--control-group-extra-size)) /
2
);
--small-control-size: calc(var(--controls-size) / 3);
--rect-control-size: calc(var(--controls-size) * 0.74);
font-family: 'emerald';
font-family: "emerald";
font-size: var(--controls-size);
text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size);
color: var(--color-light);
@ -146,31 +165,68 @@ input:-internal-autofill-selected {
/* Hide buttons on specific UIs */
/* Show #apadPreviousTab and #apadNextTab only in settings, except in touch configuration panel */
#touchControls:not([data-ui-mode^='SETTINGS']) #apadPreviousTab,
#touchControls:not([data-ui-mode^='SETTINGS']) #apadNextTab,
#touchControls:not([data-ui-mode^="SETTINGS"]) #apadPreviousTab,
#touchControls:not([data-ui-mode^="SETTINGS"]) #apadNextTab,
#touchControls:is(.config-mode) #apadPreviousTab,
#touchControls:is(.config-mode) #apadNextTab {
display: none;
}
/* Show #apadInfo only in battle */
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']) #apadInfo {
#touchControls:not([data-ui-mode="COMMAND"]):not([data-ui-mode="FIGHT"]):not(
[data-ui-mode="BALL"]
):not([data-ui-mode="TARGET_SELECT"])
#apadInfo {
display: none;
}
/* Show #apadStats only in battle and shop */
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']):not([data-ui-mode='MODIFIER_SELECT']) #apadStats {
#touchControls:not([data-ui-mode="COMMAND"]):not([data-ui-mode="FIGHT"]):not(
[data-ui-mode="BALL"]
):not([data-ui-mode="TARGET_SELECT"]):not([data-ui-mode="MODIFIER_SELECT"])
#apadStats {
display: none;
}
/* Show cycle buttons only on STARTER_SELECT and on touch configuration panel */
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX'], [data-ui-mode='POKEDEX_PAGE']) #apadOpenFilters,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX'], [data-ui-mode='POKEDEX_PAGE'], [data-ui-mode='RUN_INFO']) #apadCycleForm,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX'], [data-ui-mode='POKEDEX_PAGE'], [data-ui-mode='RUN_INFO']) #apadCycleShiny,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleNature,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX_PAGE'], [data-ui-mode='RUN_INFO']) #apadCycleAbility,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX_PAGE']) #apadCycleGender,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX']) #apadCycleTera {
#touchControls:not(.config-mode):not(
[data-ui-mode="STARTER_SELECT"],
[data-ui-mode="POKEDEX"],
[data-ui-mode="POKEDEX_PAGE"]
)
#apadOpenFilters,
#touchControls:not(.config-mode):not(
[data-ui-mode="STARTER_SELECT"],
[data-ui-mode="POKEDEX"],
[data-ui-mode="POKEDEX_PAGE"],
[data-ui-mode="RUN_INFO"]
)
#apadCycleForm,
#touchControls:not(.config-mode):not(
[data-ui-mode="STARTER_SELECT"],
[data-ui-mode="POKEDEX"],
[data-ui-mode="POKEDEX_PAGE"],
[data-ui-mode="RUN_INFO"]
)
#apadCycleShiny,
#touchControls:not(.config-mode):not([data-ui-mode="STARTER_SELECT"])
#apadCycleNature,
#touchControls:not(.config-mode):not(
[data-ui-mode="STARTER_SELECT"],
[data-ui-mode="POKEDEX_PAGE"],
[data-ui-mode="RUN_INFO"]
)
#apadCycleAbility,
#touchControls:not(.config-mode):not(
[data-ui-mode="STARTER_SELECT"],
[data-ui-mode="POKEDEX_PAGE"]
)
#apadCycleGender,
#touchControls:not(.config-mode):not(
[data-ui-mode="STARTER_SELECT"],
[data-ui-mode="POKEDEX"]
)
#apadCycleTera {
display: none;
}
@ -217,16 +273,18 @@ input:-internal-autofill-selected {
font-size: var(--small-control-size);
border-radius: 8px;
padding: 2px 8px;
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3)
calc(var(--text-shadow-size) / 3);
}
#configToolbar .button:active {
opacity: var(--touch-control-opacity)
opacity: var(--touch-control-opacity);
}
#configToolbar .orientation-label {
font-size: var(--small-control-size);
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3)
calc(var(--text-shadow-size) / 3);
}
/* dpad */
@ -270,7 +328,8 @@ input:-internal-autofill-selected {
.apad-small > .apad-label {
font-size: var(--small-control-size);
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3)
calc(var(--text-shadow-size) / 3);
}
.apad-rectangle {
@ -320,7 +379,8 @@ input:-internal-autofill-selected {
/* Layout */
#layout:fullscreen #dpad, #layout:fullscreen #apad {
#layout:fullscreen #dpad,
#layout:fullscreen #apad {
bottom: 6rem;
}

View File

@ -1,9 +1,9 @@
pre-commit:
parallel: true
commands:
eslint:
biome-lint:
glob: "*.{js,jsx,ts,tsx}"
run: npx eslint --fix {staged_files}
run: npx @biomejs/biome check --write --reporter=summary {staged_files} --no-errors-on-unmatched
stage_fixed: true
skip:
- merge
@ -11,9 +11,9 @@ pre-commit:
pre-push:
commands:
eslint:
biome-lint:
glob: "*.{js,ts,jsx,tsx}"
run: npx eslint --fix {push_files}
run: npx @biomejs/biome check --write --reporter=summary {push_files} --no-errors-on-unmatched
post-merge:
commands:

165
package-lock.json generated
View File

@ -21,6 +21,7 @@
"phaser3-rex-plugins": "^1.1.84"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@eslint/js": "^9.3.0",
"@hpcc-js/wasm": "^2.18.0",
"@stylistic/eslint-plugin-ts": "^2.6.0-beta.0",
@ -492,6 +493,170 @@
"node": ">=6.9.0"
}
},
"node_modules/@biomejs/biome": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
"dev": true,
"hasInstallScript": true,
"license": "MIT OR Apache-2.0",
"bin": {
"biome": "bin/biome"
},
"engines": {
"node": ">=14.21.3"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/biome"
},
"optionalDependencies": {
"@biomejs/cli-darwin-arm64": "1.9.4",
"@biomejs/cli-darwin-x64": "1.9.4",
"@biomejs/cli-linux-arm64": "1.9.4",
"@biomejs/cli-linux-arm64-musl": "1.9.4",
"@biomejs/cli-linux-x64": "1.9.4",
"@biomejs/cli-linux-x64-musl": "1.9.4",
"@biomejs/cli-win32-arm64": "1.9.4",
"@biomejs/cli-win32-x64": "1.9.4"
}
},
"node_modules/@biomejs/cli-darwin-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-darwin-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64-musl": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64-musl": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@bundled-es-modules/cookie": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz",

View File

@ -16,6 +16,8 @@
"typecheck": "tsc --noEmit",
"eslint": "eslint --fix .",
"eslint-ci": "eslint .",
"biome": "biome check --write --changed --no-errors-on-unmatched",
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --changed --no-errors-on-unmatched",
"docs": "typedoc",
"depcruise": "depcruise src",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
@ -26,6 +28,7 @@
"update-locales:remote": "git submodule update --progress --init --recursive --force --remote"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@eslint/js": "^9.3.0",
"@hpcc-js/wasm": "^2.18.0",
"@stylistic/eslint-plugin-ts": "^2.6.0-beta.0",

View File

@ -13,10 +13,10 @@
"1005",
"1006",
"1006",
"1007-apex",
"1007-apex",
"1008-ultimate",
"1008-ultimate",
"1007-apex-build-Disabled",
"1007-apex-build-Disabled",
"1008-ultimate-mode-Disabled",
"1008-ultimate-mode-Disabled",
"115-mega",
"115-mega",
"127-mega",
@ -185,9 +185,9 @@
"531-mega",
"569-gigantamax",
"569-gigantamax",
"6-mega",
"6-mega",
"6-mega-x",
"6-mega-x",
"6-mega-y",
"6-mega-y",
"6058",
"6058",
@ -825,8 +825,8 @@
"873",
"874",
"874",
"875-no",
"875-no",
"875-no-ice",
"875-no-ice",
"875",
"875",
"876-female",
@ -963,26 +963,26 @@
"929",
"930",
"930",
"931-blue",
"931-blue",
"931-green",
"931-green",
"931-white",
"931-white",
"931-yellow",
"931-yellow",
"931-blue-plumage-Disabled",
"931-blue-plumage-Disabled",
"931-green-plumage-Disabled",
"931-green-plumage-Disabled",
"931-white-plumage-Disabled",
"931-white-plumage-Disabled",
"931-yellow-plumage-Disabled",
"931-yellow-plumage-Disabled",
"932",
"932",
"933",
"933",
"934",
"934",
"935",
"935",
"936",
"936",
"937",
"937",
"935-Disabled",
"935-Disabled",
"936-Disabled",
"936-Disabled",
"937-Disabled",
"937-Disabled",
"938",
"938",
"939",
@ -1073,6 +1073,8 @@
"978-droopy",
"978-stretchy",
"978-stretchy",
"979-Disabled",
"979-Disabled",
"980",
"980",
"981",
@ -1131,10 +1133,10 @@
"1005b",
"1006b",
"1006b",
"1007b-apex",
"1007b-apex",
"1008b-ultimate",
"1008b-ultimate",
"1007b-apex-build-Disabled",
"1007b-apex-build-Disabled",
"1008b-ultimate-mode-Disabled",
"1008b-ultimate-mode-Disabled",
"115b-mega",
"115b-mega",
"127b-mega",
@ -1303,9 +1305,9 @@
"531b-mega",
"569b-gigantamax",
"569b-gigantamax",
"6b-mega",
"6b-mega",
"6b-mega-x",
"6b-mega-x",
"6b-mega-y",
"6b-mega-y",
"6058b",
"6058b",
@ -1943,8 +1945,8 @@
"873b",
"874b",
"874b",
"875b-no",
"875b-no",
"875b-no-ice",
"875b-no-ice",
"875b",
"875b",
"876b-female",
@ -2083,26 +2085,26 @@
"929b",
"930b",
"930b",
"931b-blue",
"931b-blue",
"931b-green",
"931b-green",
"931b-white",
"931b-white",
"931b-yellow",
"931b-yellow",
"931b-blue-plumage-Disabled",
"931b-blue-plumage-Disabled",
"931b-green-plumage-Disabled",
"931b-green-plumage-Disabled",
"931b-white-plumage-Disabled",
"931b-white-plumage-Disabled",
"931b-yellow-plumage-Disabled",
"931b-yellow-plumage-Disabled",
"932b",
"932b",
"933b",
"933b",
"934b",
"934b",
"935b",
"935b",
"936b",
"936b",
"937b",
"937b",
"935b-Disabled",
"935b-Disabled",
"936b-Disabled",
"936b-Disabled",
"937b-Disabled",
"937b-Disabled",
"938b",
"938b",
"939b",
@ -2251,10 +2253,10 @@
"1005sb",
"1006sb",
"1006sb",
"1007sb-apex",
"1007sb-apex",
"1008sb-ultimate",
"1008sb-ultimate",
"1007sb-apex-build-Disabled",
"1007sb-apex-build-Disabled",
"1008sb-ultimate-mode-Disabled",
"1008sb-ultimate-mode-Disabled",
"115sb-mega",
"115sb-mega",
"127sb-mega",
@ -3063,8 +3065,8 @@
"873sb",
"874sb",
"874sb",
"875sb-no",
"875sb-no",
"875sb-no-ice",
"875sb-no-ice",
"875sb",
"875sb",
"876sb-female",
@ -3203,26 +3205,26 @@
"929sb",
"930sb",
"930sb",
"931sb-blue",
"931sb-blue",
"931sb-green",
"931sb-green",
"931sb-white",
"931sb-white",
"931sb-yellow",
"931sb-yellow",
"931sb-blue-plumage-Disabled",
"931sb-blue-plumage-Disabled",
"931sb-green-plumage-Disabled",
"931sb-green-plumage-Disabled",
"931sb-white-plumage-Disabled",
"931sb-white-plumage-Disabled",
"931sb-yellow-plumage-Disabled",
"931sb-yellow-plumage-Disabled",
"932sb",
"932sb",
"933sb",
"933sb",
"934sb",
"934sb",
"935sb",
"935sb",
"936sb",
"936sb",
"937sb",
"937sb",
"935sb-Disabled",
"935sb-Disabled",
"936sb-Disabled",
"936sb-Disabled",
"937sb-Disabled",
"937sb-Disabled",
"938sb",
"938sb",
"939sb",
@ -3376,10 +3378,10 @@
"1005s",
"1006s",
"1006s",
"1007s-apex",
"1007s-apex",
"1008s-ultimate",
"1008s-ultimate",
"1007s-apex-build-Disabled",
"1007s-apex-build-Disabled",
"1008s-ultimate-mode-Disabled",
"1008s-ultimate-mode-Disabled",
"115s-mega",
"115s-mega",
"127s-mega",
@ -4188,8 +4190,8 @@
"873s",
"874s",
"874s",
"875s-no",
"875s-no",
"875s-no-ice",
"875s-no-ice",
"875s",
"875s",
"876s-female",
@ -4328,26 +4330,26 @@
"929s",
"930s",
"930s",
"931s-blue",
"931s-blue",
"931s-green",
"931s-green",
"931s-white",
"931s-white",
"931s-yellow",
"931s-yellow",
"931s-blue-plumage-Disabled",
"931s-blue-plumage-Disabled",
"931s-green-plumage-Disabled",
"931s-green-plumage-Disabled",
"931s-white-plumage-Disabled",
"931s-white-plumage-Disabled",
"931s-yellow-plumage-Disabled",
"931s-yellow-plumage-Disabled",
"932s",
"932s",
"933s",
"933s",
"934s",
"934s",
"935s",
"935s",
"936s",
"936s",
"937s",
"937s",
"935s-Disabled",
"935s-Disabled",
"936s-Disabled",
"936s-Disabled",
"937s-Disabled",
"937s-Disabled",
"938s",
"938s",
"939s",
@ -4438,6 +4440,8 @@
"978s-droopy",
"978s-stretchy",
"978s-stretchy",
"979s-Disabled",
"979s-Disabled",
"980s",
"980s",
"981s",
@ -4485,11 +4489,10 @@
"1000",
"1001",
"1004",
"1007-apex",
"1007-apex",
"1007-apex",
"1007-apex",
"1008-ultimate",
"1007-apex-build-Disabled",
"1007-apex-build-Disabled",
"1008-ultimate-mode-Disabled",
"1008-ultimate-mode-Disabled",
"127-mega",
"142-mega",
"150-mega",
@ -4698,21 +4701,21 @@
"933_3",
"933_3",
"934",
"935",
"935_3",
"935_3",
"936_1",
"936_1",
"936_2",
"936_2",
"936_3",
"936_3",
"937_1",
"937_1",
"937_2",
"937_2",
"937_3",
"937_3",
"935-Disabled",
"935_3-Disabled",
"935_3-Disabled",
"936_1-Disabled",
"936_1-Disabled",
"936_2-Disabled",
"936_2-Disabled",
"936_3-Disabled",
"936_3-Disabled",
"937_1-Disabled",
"937_1-Disabled",
"937_2-Disabled",
"937_2-Disabled",
"937_3-Disabled",
"937_3-Disabled",
"94-mega_1",
"94-mega_1",
"94-mega_2",
@ -4755,11 +4758,10 @@
"1000b",
"1001b",
"1004b",
"1007b-apex",
"1007b-apex",
"1007b-apex",
"1007b-apex",
"1008b-ultimate",
"1007b-apex-build-Disabled",
"1007b-apex-build-Disabled",
"1008b-ultimate-mode-Disabled",
"1008b-ultimate-mode-Disabled",
"127b-mega",
"142b-mega",
"150b-mega",
@ -4920,24 +4922,24 @@
"932b",
"933b",
"934b",
"935_1b",
"935_1b",
"935_2b",
"935_2b",
"935_3b",
"935_3b",
"936_1b",
"936_1b",
"936_2b",
"936_2b",
"936_3b",
"936_3b",
"937_1b",
"937_1b",
"937_2b",
"937_2b",
"937_3b",
"937_3b",
"935_1b-Disabled",
"935_1b-Disabled",
"935_2b-Disabled",
"935_2b-Disabled",
"935_3b-Disabled",
"935_3b-Disabled",
"936_1b-Disabled",
"936_1b-Disabled",
"936_2b-Disabled",
"936_2b-Disabled",
"936_3b-Disabled",
"936_3b-Disabled",
"937_1b-Disabled",
"937_1b-Disabled",
"937_2b-Disabled",
"937_2b-Disabled",
"937_3b-Disabled",
"937_3b-Disabled",
"94b-mega",
"948b",
"949b",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,440 @@
{
"textures": [
{
"image": "types_ca-ES.png",
"format": "RGBA8888",
"size": {
"w": 32,
"h": 280
},
"scale": 1,
"frames": [
{
"filename": "unknown",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
}
},
{
"filename": "bug",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 14,
"w": 32,
"h": 14
}
},
{
"filename": "dark",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 28,
"w": 32,
"h": 14
}
},
{
"filename": "dragon",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 42,
"w": 32,
"h": 14
}
},
{
"filename": "electric",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 56,
"w": 32,
"h": 14
}
},
{
"filename": "fairy",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 70,
"w": 32,
"h": 14
}
},
{
"filename": "fighting",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 84,
"w": 32,
"h": 14
}
},
{
"filename": "fire",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 98,
"w": 32,
"h": 14
}
},
{
"filename": "flying",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 112,
"w": 32,
"h": 14
}
},
{
"filename": "ghost",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 126,
"w": 32,
"h": 14
}
},
{
"filename": "grass",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 140,
"w": 32,
"h": 14
}
},
{
"filename": "ground",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 154,
"w": 32,
"h": 14
}
},
{
"filename": "ice",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 168,
"w": 32,
"h": 14
}
},
{
"filename": "normal",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 182,
"w": 32,
"h": 14
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 196,
"w": 32,
"h": 14
}
},
{
"filename": "psychic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 210,
"w": 32,
"h": 14
}
},
{
"filename": "rock",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 224,
"w": 32,
"h": 14
}
},
{
"filename": "steel",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 238,
"w": 32,
"h": 14
}
},
{
"filename": "water",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 252,
"w": 32,
"h": 14
}
},
{
"filename": "stellar",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 32,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 32,
"h": 14
},
"frame": {
"x": 0,
"y": 266,
"w": 32,
"h": 14
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:f14cf47d9a8f1d40c8e03aa6ba00fff3:6fc4227b57a95d429a1faad4280f7ec8:5961efbfbf4c56b8745347e7a663a32f$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
public/images/types_de.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -1 +1 @@
Subproject commit 6b3f37cb351552721232f4dabefa17bddb5b9004
Subproject commit cd4057af258b659ba2c1ed2778bb2793fa1f6141

View File

@ -8,13 +8,25 @@ export let loggedInUser: UserInfo | null = null;
export const clientSessionId = Utils.randomString(32);
export function initLoggedInUser(): void {
loggedInUser = { username: "Guest", lastSessionSlot: -1, discordId: "", googleId: "", hasAdminRole: false };
loggedInUser = {
username: "Guest",
lastSessionSlot: -1,
discordId: "",
googleId: "",
hasAdminRole: false,
};
}
export function updateUserInfo(): Promise<[boolean, number]> {
return new Promise<[boolean, number]>(resolve => {
if (bypassLogin) {
loggedInUser = { username: "Guest", lastSessionSlot: -1, discordId: "", googleId: "", hasAdminRole: false };
loggedInUser = {
username: "Guest",
lastSessionSlot: -1,
discordId: "",
googleId: "",
hasAdminRole: false,
};
let lastSessionSlot = -1;
for (let s = 0; s < 5; s++) {
if (localStorage.getItem(`sessionData${s ? s : ""}_${loggedInUser.username}`)) {
@ -41,10 +53,9 @@ export function updateUserInfo(): Promise<[boolean, number]> {
if (!accountInfo) {
resolve([false, status]);
return;
} else {
}
loggedInUser = accountInfo;
resolve([true, 200]);
}
});
});
}

File diff suppressed because it is too large Load Diff

View File

@ -50,7 +50,7 @@ export enum BattleType {
WILD,
TRAINER,
CLEAR,
MYSTERY_ENCOUNTER
MYSTERY_ENCOUNTER,
}
export enum BattlerIndex {
@ -58,7 +58,7 @@ export enum BattlerIndex {
PLAYER,
PLAYER_2,
ENEMY,
ENEMY_2
ENEMY_2,
}
export interface TurnCommand {
@ -71,12 +71,12 @@ export interface TurnCommand {
}
export interface FaintLogEntry {
pokemon: Pokemon,
turn: number
pokemon: Pokemon;
turn: number;
}
interface TurnCommands {
[key: number]: TurnCommand | null
[key: number]: TurnCommand | null;
}
export default class Battle {
@ -89,19 +89,19 @@ export default class Battle {
public enemyParty: EnemyPokemon[] = [];
public seenEnemyPartyMemberIds: Set<number> = new Set<number>();
public double: boolean;
public started: boolean = false;
public enemySwitchCounter: number = 0;
public turn: number = 0;
public started = false;
public enemySwitchCounter = 0;
public turn = 0;
public preTurnCommands: TurnCommands;
public turnCommands: TurnCommands;
public playerParticipantIds: Set<number> = new Set<number>();
public battleScore: number = 0;
public battleScore = 0;
public postBattleLoot: PokemonHeldItemModifier[] = [];
public escapeAttempts: number = 0;
public escapeAttempts = 0;
public lastMove: Moves;
public battleSeed: string = Utils.randomString(16, true);
private battleSeedState: string | null = null;
public moneyScattered: number = 0;
public moneyScattered = 0;
/** Primarily for double battles, keeps track of last enemy and player pokemon that triggered its ability or used a move */
public lastEnemyInvolved: number;
public lastPlayerInvolved: number;
@ -111,7 +111,7 @@ export default class Battle {
* This is saved here since we encounter a new enemy every wave.
* {@linkcode globalScene.arena.playerFaints} is the corresponding faint counter for the player and needs to be save across waves (reset every arena encounter).
*/
public enemyFaints: number = 0;
public enemyFaints = 0;
public playerFaintsHistory: FaintLogEntry[] = [];
public enemyFaintsHistory: FaintLogEntry[] = [];
@ -119,15 +119,16 @@ export default class Battle {
/** If the current battle is a Mystery Encounter, this will always be defined */
public mysteryEncounter?: MysteryEncounter;
private rngCounter: number = 0;
private rngCounter = 0;
constructor(gameMode: GameMode, waveIndex: number, battleType: BattleType, trainer?: Trainer, double: boolean = false) {
constructor(gameMode: GameMode, waveIndex: number, battleType: BattleType, trainer?: Trainer, double = false) {
this.gameMode = gameMode;
this.waveIndex = waveIndex;
this.battleType = battleType;
this.trainer = trainer ?? null;
this.initBattleSpec();
this.enemyLevels = battleType !== BattleType.TRAINER
this.enemyLevels =
battleType !== BattleType.TRAINER
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
: trainer?.getPartyLevels(this.waveIndex);
this.double = double;
@ -194,12 +195,19 @@ export default class Battle {
}
addPostBattleLoot(enemyPokemon: EnemyPokemon): void {
this.postBattleLoot.push(...globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, false).map(i => {
this.postBattleLoot.push(
...globalScene
.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable,
false,
)
.map(i => {
const ret = i as PokemonHeldItemModifier;
//@ts-ignore - this is awful to fix/change
ret.pokemonId = null;
return ret;
}));
}),
);
}
pickUpScatteredMoney(): void {
@ -214,7 +222,9 @@ export default class Battle {
const userLocale = navigator.language || "en-US";
const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale);
const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount });
const message = i18next.t("battle:moneyPickedUp", {
moneyAmount: formattedMoneyAmount,
});
globalScene.queueMessage(message, undefined, true);
globalScene.currentBattle.moneyScattered = 0;
@ -227,13 +237,17 @@ export default class Battle {
}
for (const p of globalScene.getEnemyParty()) {
if (p.isBoss()) {
partyMemberTurnMultiplier *= (p.bossSegments / 1.5) / globalScene.getEnemyParty().length;
partyMemberTurnMultiplier *= p.bossSegments / 1.5 / globalScene.getEnemyParty().length;
}
}
const turnMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")(1 - Math.min(this.turn - 2, 10 * partyMemberTurnMultiplier) / (10 * partyMemberTurnMultiplier));
const turnMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")(
1 - Math.min(this.turn - 2, 10 * partyMemberTurnMultiplier) / (10 * partyMemberTurnMultiplier),
);
const finalBattleScore = Math.ceil(this.battleScore * turnMultiplier);
globalScene.score += finalBattleScore;
console.log(`Battle Score: ${finalBattleScore} (${this.turn - 1} Turns x${Math.floor(turnMultiplier * 100) / 100})`);
console.log(
`Battle Score: ${finalBattleScore} (${this.turn - 1} Turns x${Math.floor(turnMultiplier * 100) / 100})`,
);
console.log(`Total Score: ${globalScene.score}`);
globalScene.updateScoreText();
}
@ -243,16 +257,20 @@ export default class Battle {
// Music is overridden for MEs during ME onInit()
// Should not use any BGM overrides before swapping from DEFAULT mode
return null;
} else if (this.battleType === BattleType.TRAINER || this.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
}
if (
this.battleType === BattleType.TRAINER ||
this.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE
) {
if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) {
return `encounter_${this.trainer?.getEncounterBgm()}`;
}
if (globalScene.musicPreference === MusicPreference.GENFIVE) {
return this.trainer?.getBattleBgm() ?? null;
} else {
}
return this.trainer?.getMixedBattleBgm() ?? null;
}
} else if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) {
if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) {
return "end_summit";
}
const wildOpponents = globalScene.getEnemyParty();
@ -281,7 +299,8 @@ export default class Battle {
}
return "battle_legendary_unova";
}
} else if (globalScene.musicPreference === MusicPreference.ALLGENS) {
}
if (globalScene.musicPreference === MusicPreference.ALLGENS) {
switch (pokemon.species.speciesId) {
case Species.ARTICUNO:
case Species.ZAPDOS:
@ -434,7 +453,7 @@ export default class Battle {
* @param min The minimum integer to pick, default `0`
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
*/
randSeedInt(range: number, min: number = 0): number {
randSeedInt(range: number, min = 0): number {
if (range <= 1) {
return min;
}
@ -467,7 +486,13 @@ export default class Battle {
export class FixedBattle extends Battle {
constructor(waveIndex: number, config: FixedBattleConfig) {
super(globalScene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer() : undefined, config.double);
super(
globalScene.gameMode,
waveIndex,
config.battleType,
config.battleType === BattleType.TRAINER ? config.getTrainer() : undefined,
config.double,
);
if (config.getEnemyParty) {
this.enemyParty = config.getEnemyParty();
}
@ -516,7 +541,6 @@ export class FixedBattleConfig {
}
}
/**
* Helper function to generate a random trainer for evil team trainers and the elite 4/champion
* @param trainerPool The TrainerType or list of TrainerTypes that can possibly be generated
@ -524,31 +548,44 @@ export class FixedBattleConfig {
* @param seedOffset the seed offset to use for the random generation of the trainer
* @returns the generated trainer
*/
export function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], randomGender: boolean = false, seedOffset: number = 0): GetTrainerFunc {
export function getRandomTrainerFunc(
trainerPool: (TrainerType | TrainerType[])[],
randomGender = false,
seedOffset = 0,
): GetTrainerFunc {
return () => {
const rand = Utils.randSeedInt(trainerPool.length);
const trainerTypes: TrainerType[] = [];
globalScene.executeWithSeedOffset(() => {
for (const trainerPoolEntry of trainerPool) {
const trainerType = Array.isArray(trainerPoolEntry)
? Utils.randSeedItem(trainerPoolEntry)
: trainerPoolEntry;
const trainerType = Array.isArray(trainerPoolEntry) ? Utils.randSeedItem(trainerPoolEntry) : trainerPoolEntry;
trainerTypes.push(trainerType);
}
}, seedOffset);
let trainerGender = TrainerVariant.DEFAULT;
if (randomGender) {
trainerGender = (Utils.randInt(2) === 0) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT;
trainerGender = Utils.randInt(2) === 0 ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT;
}
/* 1/3 chance for evil team grunts to be double battles */
const evilTeamGrunts = [ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ];
const evilTeamGrunts = [
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
];
const isEvilTeamGrunt = evilTeamGrunts.includes(trainerTypes[rand]);
if (trainerConfigs[trainerTypes[rand]].hasDouble && isEvilTeamGrunt) {
return new Trainer(trainerTypes[rand], (Utils.randInt(3) === 0) ? TrainerVariant.DOUBLE : trainerGender);
return new Trainer(trainerTypes[rand], Utils.randInt(3) === 0 ? TrainerVariant.DOUBLE : trainerGender);
}
return new Trainer(trainerTypes[rand], trainerGender);
@ -556,7 +593,7 @@ export function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[
}
export interface FixedBattleConfigs {
[key: number]: FixedBattleConfig
[key: number]: FixedBattleConfig;
}
/**
* Youngster/Lass on 5
@ -568,51 +605,355 @@ export interface FixedBattleConfigs {
* Champion on 190
*/
export const classicFixedBattles: FixedBattleConfigs = {
[ClassicFixedBossWaves.TOWN_YOUNGSTER]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(() => new Trainer(TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
[ClassicFixedBossWaves.RIVAL_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
[ClassicFixedBossWaves.RIVAL_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_2, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }),
[ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)),
[ClassicFixedBossWaves.RIVAL_3]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_3, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }),
[ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)),
[ClassicFixedBossWaves.EVIL_GRUNT_3]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)),
[ClassicFixedBossWaves.EVIL_ADMIN_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.COLRESS ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA, [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]], true)),
[ClassicFixedBossWaves.RIVAL_4]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_4, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }),
[ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)),
[ClassicFixedBossWaves.EVIL_ADMIN_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.COLRESS ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA, [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]], true, 1)),
[ClassicFixedBossWaves.EVIL_BOSS_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.MAXIE, TrainerType.ARCHIE, TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE, TrainerType.LUSAMINE, TrainerType.GUZMA, TrainerType.ROSE, TrainerType.PENNY ]))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }),
[ClassicFixedBossWaves.RIVAL_5]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_5, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }),
[ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_2, TrainerType.MAXIE_2, TrainerType.ARCHIE_2, TrainerType.CYRUS_2, TrainerType.GHETSIS_2, TrainerType.LYSANDRE_2, TrainerType.LUSAMINE_2, TrainerType.GUZMA_2, TrainerType.ROSE_2, TrainerType.PENNY_2 ]))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }),
[ClassicFixedBossWaves.ELITE_FOUR_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, [ TrainerType.HALA, TrainerType.MOLAYNE ], TrainerType.MARNIE_ELITE, TrainerType.RIKA, TrainerType.CRISPIN ])),
[ClassicFixedBossWaves.ELITE_FOUR_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY, TrainerType.AMARYS ])),
[ClassicFixedBossWaves.ELITE_FOUR_3]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, [ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ], TrainerType.LARRY_ELITE, TrainerType.LACEY ])),
[ClassicFixedBossWaves.ELITE_FOUR_4]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL, TrainerType.DRAYTON ])),
[ClassicFixedBossWaves.CHAMPION]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BLUE, [ TrainerType.RED, TrainerType.LANCE_CHAMPION ], [ TrainerType.STEVEN, TrainerType.WALLACE ], TrainerType.CYNTHIA, [ TrainerType.ALDER, TrainerType.IRIS ], TrainerType.DIANTHA, [ TrainerType.KUKUI, TrainerType.HAU ], [ TrainerType.LEON, TrainerType.MUSTARD ], [ TrainerType.GEETA, TrainerType.NEMONA ], TrainerType.KIERAN ])),
[ClassicFixedBossWaves.RIVAL_6]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_6, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false })
[ClassicFixedBossWaves.TOWN_YOUNGSTER]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() => new Trainer(TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT),
),
[ClassicFixedBossWaves.RIVAL_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
),
[ClassicFixedBossWaves.RIVAL_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_2,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.RIVAL_3]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_3,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.EVIL_GRUNT_3]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.EVIL_ADMIN_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
[TrainerType.TABITHA, TrainerType.COURTNEY],
[TrainerType.MATT, TrainerType.SHELLY],
[TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN],
[TrainerType.ZINZOLIN, TrainerType.COLRESS],
[TrainerType.XEROSIC, TrainerType.BRYONY],
TrainerType.FABA,
TrainerType.PLUMERIA,
TrainerType.OLEANA,
[TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI],
],
true,
),
),
[ClassicFixedBossWaves.RIVAL_4]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_4,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
TrainerType.ROCKET_GRUNT,
TrainerType.MAGMA_GRUNT,
TrainerType.AQUA_GRUNT,
TrainerType.GALACTIC_GRUNT,
TrainerType.PLASMA_GRUNT,
TrainerType.FLARE_GRUNT,
TrainerType.AETHER_GRUNT,
TrainerType.SKULL_GRUNT,
TrainerType.MACRO_GRUNT,
TrainerType.STAR_GRUNT,
],
true,
),
),
[ClassicFixedBossWaves.EVIL_ADMIN_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc(
[
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
[TrainerType.TABITHA, TrainerType.COURTNEY],
[TrainerType.MATT, TrainerType.SHELLY],
[TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN],
[TrainerType.ZINZOLIN, TrainerType.COLRESS],
[TrainerType.XEROSIC, TrainerType.BRYONY],
TrainerType.FABA,
TrainerType.PLUMERIA,
TrainerType.OLEANA,
[TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI],
],
true,
1,
),
),
[ClassicFixedBossWaves.EVIL_BOSS_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.ROCKET_BOSS_GIOVANNI_1,
TrainerType.MAXIE,
TrainerType.ARCHIE,
TrainerType.CYRUS,
TrainerType.GHETSIS,
TrainerType.LYSANDRE,
TrainerType.LUSAMINE,
TrainerType.GUZMA,
TrainerType.ROSE,
TrainerType.PENNY,
]),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.RIVAL_5]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_5,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.ROCKET_BOSS_GIOVANNI_2,
TrainerType.MAXIE_2,
TrainerType.ARCHIE_2,
TrainerType.CYRUS_2,
TrainerType.GHETSIS_2,
TrainerType.LYSANDRE_2,
TrainerType.LUSAMINE_2,
TrainerType.GUZMA_2,
TrainerType.ROSE_2,
TrainerType.PENNY_2,
]),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
allowLuckUpgrades: false,
}),
[ClassicFixedBossWaves.ELITE_FOUR_1]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.LORELEI,
TrainerType.WILL,
TrainerType.SIDNEY,
TrainerType.AARON,
TrainerType.SHAUNTAL,
TrainerType.MALVA,
[TrainerType.HALA, TrainerType.MOLAYNE],
TrainerType.MARNIE_ELITE,
TrainerType.RIKA,
TrainerType.CRISPIN,
]),
),
[ClassicFixedBossWaves.ELITE_FOUR_2]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.BRUNO,
TrainerType.KOGA,
TrainerType.PHOEBE,
TrainerType.BERTHA,
TrainerType.MARSHAL,
TrainerType.SIEBOLD,
TrainerType.OLIVIA,
TrainerType.NESSA_ELITE,
TrainerType.POPPY,
TrainerType.AMARYS,
]),
),
[ClassicFixedBossWaves.ELITE_FOUR_3]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.AGATHA,
TrainerType.BRUNO,
TrainerType.GLACIA,
TrainerType.FLINT,
TrainerType.GRIMSLEY,
TrainerType.WIKSTROM,
TrainerType.ACEROLA,
[TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE],
TrainerType.LARRY_ELITE,
TrainerType.LACEY,
]),
),
[ClassicFixedBossWaves.ELITE_FOUR_4]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.LANCE,
TrainerType.KAREN,
TrainerType.DRAKE,
TrainerType.LUCIAN,
TrainerType.CAITLIN,
TrainerType.DRASNA,
TrainerType.KAHILI,
TrainerType.RAIHAN_ELITE,
TrainerType.HASSEL,
TrainerType.DRAYTON,
]),
),
[ClassicFixedBossWaves.CHAMPION]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
.setGetTrainerFunc(
getRandomTrainerFunc([
TrainerType.BLUE,
[TrainerType.RED, TrainerType.LANCE_CHAMPION],
[TrainerType.STEVEN, TrainerType.WALLACE],
TrainerType.CYNTHIA,
[TrainerType.ALDER, TrainerType.IRIS],
TrainerType.DIANTHA,
[TrainerType.KUKUI, TrainerType.HAU],
[TrainerType.LEON, TrainerType.MUSTARD],
[TrainerType.GEETA, TrainerType.NEMONA],
TrainerType.KIERAN,
]),
),
[ClassicFixedBossWaves.RIVAL_6]: new FixedBattleConfig()
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(
() =>
new Trainer(
TrainerType.RIVAL_6,
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
),
)
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.GREAT,
ModifierTier.GREAT,
],
allowLuckUpgrades: false,
}),
};

View File

@ -77,7 +77,7 @@ const cfg_keyboard_qwerty = {
KEY_RIGHT_BRACKET: Phaser.Input.Keyboard.KeyCodes.CLOSED_BRACKET,
KEY_SEMICOLON: Phaser.Input.Keyboard.KeyCodes.SEMICOLON,
KEY_BACKSPACE: Phaser.Input.Keyboard.KeyCodes.BACKSPACE,
KEY_ALT: Phaser.Input.Keyboard.KeyCodes.ALT
KEY_ALT: Phaser.Input.Keyboard.KeyCodes.ALT,
},
icons: {
KEY_A: "A.png",
@ -131,7 +131,6 @@ const cfg_keyboard_qwerty = {
KEY_F11: "F11.png",
KEY_F12: "F12.png",
KEY_PAGE_DOWN: "PAGE_DOWN.png",
KEY_PAGE_UP: "PAGE_UP.png",
@ -163,7 +162,7 @@ const cfg_keyboard_qwerty = {
KEY_SEMICOLON: "SEMICOLON.png",
KEY_BACKSPACE: "BACK.png",
KEY_ALT: "ALT.png"
KEY_ALT: "ALT.png",
},
settings: {
[SettingKeyboard.Button_Up]: Button.UP,
@ -274,7 +273,7 @@ const cfg_keyboard_qwerty = {
KEY_LEFT_BRACKET: -1,
KEY_RIGHT_BRACKET: -1,
KEY_SEMICOLON: -1,
KEY_ALT: -1
KEY_ALT: -1,
},
blacklist: [
"KEY_ENTER",
@ -287,7 +286,7 @@ const cfg_keyboard_qwerty = {
"KEY_ARROW_RIGHT",
"KEY_DEL",
"KEY_HOME",
]
],
};
export default cfg_keyboard_qwerty;

View File

@ -93,7 +93,7 @@ export function getIconWithSettingName(config, settingName) {
}
export function getIconForLatestInput(configs, source, devices, settingName) {
let config;
let config: any; // TODO: refine type
if (source === "gamepad") {
config = configs[devices[Device.GAMEPAD]];
} else {
@ -102,7 +102,7 @@ export function getIconForLatestInput(configs, source, devices, settingName) {
const icon = getIconWithSettingName(config, settingName);
if (!icon) {
const isAlt = settingName.includes("ALT_");
let altSettingName;
let altSettingName: string;
if (isAlt) {
altSettingName = settingName.split("ALT_").splice(1)[0];
} else {
@ -115,7 +115,10 @@ export function getIconForLatestInput(configs, source, devices, settingName) {
export function assign(config, settingNameTarget, keycode): boolean {
// first, we need to check if this keycode is already used on another settingName
if (!canIAssignThisKey(config, getKeyWithKeycode(config, keycode)) || !canIOverrideThisSetting(config, settingNameTarget)) {
if (
!canIAssignThisKey(config, getKeyWithKeycode(config, keycode)) ||
!canIOverrideThisSetting(config, settingNameTarget)
) {
return false;
}
const previousSettingName = getSettingNameWithKeycode(config, keycode);

View File

@ -24,7 +24,7 @@ const pad_dualshock = {
LC_S: 13,
LC_W: 14,
LC_E: 15,
TOUCH: 17
TOUCH: 17,
},
icons: {
RC_S: "CROSS.png",
@ -43,7 +43,7 @@ const pad_dualshock = {
LC_S: "DOWN.png",
LC_W: "LEFT.png",
LC_E: "RIGHT.png",
TOUCH: "TOUCH.png"
TOUCH: "TOUCH.png",
},
settings: {
[SettingGamepad.Button_Up]: Button.UP,
@ -62,7 +62,7 @@ const pad_dualshock = {
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
[SettingGamepad.Button_Submit]: Button.SUBMIT
[SettingGamepad.Button_Submit]: Button.SUBMIT,
},
default: {
LC_N: SettingGamepad.Button_Up,

View File

@ -23,7 +23,7 @@ const pad_generic = {
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15
LC_E: 15,
},
icons: {
RC_S: "XB_Letter_A_OL.png",
@ -59,7 +59,7 @@ const pad_generic = {
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
},
default: {
LC_N: SettingGamepad.Button_Up,
@ -77,14 +77,9 @@ const pad_generic = {
LT: SettingGamepad.Button_Cycle_Gender,
RT: SettingGamepad.Button_Cycle_Ability,
LS: SettingGamepad.Button_Speed_Up,
RS: SettingGamepad.Button_Slow_Down
RS: SettingGamepad.Button_Slow_Down,
},
blacklist: [
"LC_N",
"LC_S",
"LC_W",
"LC_E",
]
blacklist: ["LC_N", "LC_S", "LC_W", "LC_E"],
};
export default pad_generic;

View File

@ -60,7 +60,7 @@ const pad_procon = {
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
},
default: {
LC_N: SettingGamepad.Button_Up,
@ -78,7 +78,7 @@ const pad_procon = {
LT: SettingGamepad.Button_Cycle_Gender,
RT: SettingGamepad.Button_Cycle_Ability,
LS: SettingGamepad.Button_Speed_Up,
RS: SettingGamepad.Button_Slow_Down
RS: SettingGamepad.Button_Slow_Down,
},
};

View File

@ -19,7 +19,7 @@ const pad_unlicensedSNES = {
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15
LC_E: 15,
},
icons: {
RC_S: "XB_Letter_A_OL.png",
@ -51,7 +51,7 @@ const pad_unlicensedSNES = {
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
},
default: {
LC_N: SettingGamepad.Button_Up,
@ -69,7 +69,7 @@ const pad_unlicensedSNES = {
LT: -1,
RT: -1,
LS: -1,
RS: -1
RS: -1,
},
};

View File

@ -23,7 +23,7 @@ const pad_xbox360 = {
LC_N: 12,
LC_S: 13,
LC_W: 14,
LC_E: 15
LC_E: 15,
},
icons: {
RC_S: "XB_Letter_A_OL.png",
@ -59,7 +59,7 @@ const pad_xbox360 = {
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
},
default: {
LC_N: SettingGamepad.Button_Up,
@ -77,7 +77,7 @@ const pad_xbox360 = {
LT: SettingGamepad.Button_Cycle_Gender,
RT: SettingGamepad.Button_Cycle_Ability,
LS: SettingGamepad.Button_Speed_Up,
RS: SettingGamepad.Button_Slow_Down
RS: SettingGamepad.Button_Slow_Down,
},
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import { allMoves } from "#app/data/move";
import { allMoves } from "#app/data/moves/move";
import * as Utils from "#app/utils";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
@ -591,7 +591,7 @@ function parseEggMoves(content: string): void {
const speciesValues = Utils.getEnumValues(Species);
const lines = content.split(/\n/g);
lines.forEach((line, l) => {
for (const line of lines) {
const cols = line.split(",").slice(0, 5);
const moveNames = allMoves.map(m => m.name.replace(/ \([A-Z]\)$/, "").toLowerCase());
const enumSpeciesName = cols[0].toUpperCase().replace(/[ -]/g, "_");
@ -612,7 +612,7 @@ function parseEggMoves(content: string): void {
if (eggMoves.find(m => m !== Moves.NONE)) {
output += `[Species.${Species[species]}]: [ ${eggMoves.map(m => `Moves.${Moves[m]}`).join(", ")} ],\n`;
}
});
}
console.log(output);
}

View File

@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
import { Gender } from "#app/data/gender";
import { PokeballType } from "#enums/pokeball";
import type Pokemon from "#app/field/pokemon";
import { Type } from "#enums/type";
import { PokemonType } from "#enums/pokemon-type";
import * as Utils from "#app/utils";
import { WeatherType } from "#enums/weather-type";
import { Nature } from "#enums/nature";
@ -92,7 +92,7 @@ export class SpeciesFormEvolution {
public item: EvolutionItem | null;
public condition: SpeciesEvolutionCondition | null;
public wildDelay: SpeciesWildEvolutionDelay;
public description: string = "";
public description = "";
constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) {
this.speciesId = speciesId;
@ -206,7 +206,7 @@ class FriendshipTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition {
super(p => p.friendship >= amount && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT));
this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ];
} else {
super(p => false);
super(_p => false);
this.timesOfDay = [];
}
this.amount = amount;
@ -216,12 +216,12 @@ class FriendshipTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition {
class FriendshipMoveTypeEvolutionCondition extends SpeciesEvolutionCondition {
public amount: number;
public type: Type;
constructor(amount: number, type: Type) {
public type: PokemonType;
constructor(amount: number, type: PokemonType) {
super(p => p.friendship >= amount && !!p.getMoveset().find(m => m?.getMove().type === type));
this.amount = amount;
this.type = type;
this.description = i18next.t("pokemonEvolutions:friendshipMoveType", { type: i18next.t(`pokemonInfo:Type.${Type[this.type]}`) });
this.description = i18next.t("pokemonEvolutions:friendshipMoveType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) });
}
}
@ -233,11 +233,11 @@ class ShedinjaEvolutionCondition extends SpeciesEvolutionCondition {
}
class PartyTypeEvolutionCondition extends SpeciesEvolutionCondition {
public type: Type;
constructor(type: Type) {
public type: PokemonType;
constructor(type: PokemonType) {
super(() => !!globalScene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(type) > -1));
this.type = type;
this.description = i18next.t("pokemonEvolutions:partyType", { type: i18next.t(`pokemonInfo:Type.${Type[this.type]}`) });
this.description = i18next.t("pokemonEvolutions:partyType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) });
}
}
@ -260,11 +260,11 @@ class WeatherEvolutionCondition extends SpeciesEvolutionCondition {
}
class MoveTypeEvolutionCondition extends SpeciesEvolutionCondition {
public type: Type;
constructor(type: Type) {
public type: PokemonType;
constructor(type: PokemonType) {
super(p => p.moveset.filter(m => m?.getMove().type === type).length > 0);
this.type = type;
this.description = i18next.t("pokemonEvolutions:moveType", { type: i18next.t(`pokemonInfo:Type.${Type[this.type]}`) });
this.description = i18next.t("pokemonEvolutions:moveType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) });
}
}
@ -1103,7 +1103,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.GOGOAT, 32, null, null)
],
[Species.PANCHAM]: [
new SpeciesEvolution(Species.PANGORO, 32, null, new PartyTypeEvolutionCondition(Type.DARK), SpeciesWildEvolutionDelay.MEDIUM)
new SpeciesEvolution(Species.PANGORO, 32, null, new PartyTypeEvolutionCondition(PokemonType.DARK), SpeciesWildEvolutionDelay.MEDIUM)
],
[Species.ESPURR]: [
new SpeciesFormEvolution(Species.MEOWSTIC, "", "female", 25, null, new GenderEvolutionCondition(Gender.FEMALE)),
@ -1519,8 +1519,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.STARMIE, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG)
],
[Species.EEVEE]: [
new SpeciesFormEvolution(Species.SYLVEON, "", "", 1, null, new FriendshipMoveTypeEvolutionCondition(120, Type.FAIRY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.SYLVEON, "partner", "", 1, null, new FriendshipMoveTypeEvolutionCondition(120, Type.FAIRY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.SYLVEON, "", "", 1, null, new FriendshipMoveTypeEvolutionCondition(120, PokemonType.FAIRY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.SYLVEON, "partner", "", 1, null, new FriendshipMoveTypeEvolutionCondition(120, PokemonType.FAIRY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ESPEON, "", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "day"), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ESPEON, "partner", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "day"), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.UMBREON, "", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "night"), SpeciesWildEvolutionDelay.LONG),
@ -1758,7 +1758,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.GENGAR, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.ONIX]: [
new SpeciesEvolution(Species.STEELIX, 1, EvolutionItem.LINKING_CORD, new MoveTypeEvolutionCondition(Type.STEEL), SpeciesWildEvolutionDelay.VERY_LONG)
new SpeciesEvolution(Species.STEELIX, 1, EvolutionItem.LINKING_CORD, new MoveTypeEvolutionCondition(PokemonType.STEEL), SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.RHYDON]: [
new SpeciesEvolution(Species.RHYPERIOR, 1, EvolutionItem.PROTECTOR, null, SpeciesWildEvolutionDelay.VERY_LONG)
@ -1767,7 +1767,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.KINGDRA, 1, EvolutionItem.DRAGON_SCALE, null, SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.SCYTHER]: [
new SpeciesEvolution(Species.SCIZOR, 1, EvolutionItem.LINKING_CORD, new MoveTypeEvolutionCondition(Type.STEEL), SpeciesWildEvolutionDelay.VERY_LONG),
new SpeciesEvolution(Species.SCIZOR, 1, EvolutionItem.LINKING_CORD, new MoveTypeEvolutionCondition(PokemonType.STEEL), SpeciesWildEvolutionDelay.VERY_LONG),
new SpeciesEvolution(Species.KLEAVOR, 1, EvolutionItem.BLACK_AUGURITE, null, SpeciesWildEvolutionDelay.VERY_LONG)
],
[Species.ELECTABUZZ]: [
@ -1898,7 +1898,7 @@ export function initPokemonPrevolutions(): void {
if (ev.evoFormKey && megaFormKeys.indexOf(ev.evoFormKey) > -1) {
continue;
}
pokemonPrevolutions[ev.speciesId] = parseInt(pk) as Species;
pokemonPrevolutions[ev.speciesId] = Number.parseInt(pk) as Species;
}
});
}

View File

@ -0,0 +1,162 @@
import { Species } from "#enums/species";
export type SignatureSpecies = {
[key in string]: (Species | Species[])[];
};
/*
* The signature species for each Gym Leader, Elite Four member, and Champion.
* The key is the trainer type, and the value is an array of Species or Species arrays.
* This is in a separate const so it can be accessed from other places and not just the trainerConfigs
*/
export const signatureSpecies: SignatureSpecies = {
// Gym Leaders- Kanto
BROCK: [Species.GEODUDE, Species.ONIX],
MISTY: [Species.STARYU, Species.PSYDUCK],
LT_SURGE: [Species.VOLTORB, Species.PIKACHU, Species.ELECTABUZZ],
ERIKA: [Species.ODDISH, Species.BELLSPROUT, Species.TANGELA, Species.HOPPIP],
JANINE: [Species.VENONAT, Species.SPINARAK, Species.ZUBAT],
SABRINA: [Species.ABRA, Species.MR_MIME, Species.ESPEON],
BLAINE: [Species.GROWLITHE, Species.PONYTA, Species.MAGMAR],
GIOVANNI: [Species.SANDILE, Species.MURKROW, Species.NIDORAN_M, Species.NIDORAN_F],
// Gym Leaders- Johto
FALKNER: [Species.PIDGEY, Species.HOOTHOOT, Species.DODUO],
BUGSY: [Species.SCYTHER, Species.HERACROSS, Species.SHUCKLE, Species.PINSIR],
WHITNEY: [Species.JIGGLYPUFF, Species.MILTANK, Species.AIPOM, Species.GIRAFARIG],
MORTY: [Species.GASTLY, Species.MISDREAVUS, Species.SABLEYE],
CHUCK: [Species.POLIWRATH, Species.MANKEY],
JASMINE: [Species.MAGNEMITE, Species.STEELIX],
PRYCE: [Species.SEEL, Species.SWINUB],
CLAIR: [Species.DRATINI, Species.HORSEA, Species.GYARADOS],
// Gym Leaders- Hoenn
ROXANNE: [Species.GEODUDE, Species.NOSEPASS],
BRAWLY: [Species.MACHOP, Species.MAKUHITA],
WATTSON: [Species.MAGNEMITE, Species.VOLTORB, Species.ELECTRIKE],
FLANNERY: [Species.SLUGMA, Species.TORKOAL, Species.NUMEL],
NORMAN: [Species.SLAKOTH, Species.SPINDA, Species.ZIGZAGOON, Species.KECLEON],
WINONA: [Species.SWABLU, Species.WINGULL, Species.TROPIUS, Species.SKARMORY],
TATE: [Species.SOLROCK, Species.NATU, Species.CHIMECHO, Species.GALLADE],
LIZA: [Species.LUNATONE, Species.SPOINK, Species.BALTOY, Species.GARDEVOIR],
JUAN: [Species.HORSEA, Species.BARBOACH, Species.SPHEAL, Species.RELICANTH],
// Gym Leaders- Sinnoh
ROARK: [Species.CRANIDOS, Species.LARVITAR, Species.GEODUDE],
GARDENIA: [Species.ROSELIA, Species.TANGELA, Species.TURTWIG],
MAYLENE: [Species.LUCARIO, Species.MEDITITE, Species.CHIMCHAR],
CRASHER_WAKE: [Species.BUIZEL, Species.WOOPER, Species.PIPLUP, Species.MAGIKARP],
FANTINA: [Species.MISDREAVUS, Species.DRIFLOON, Species.SPIRITOMB],
BYRON: [Species.SHIELDON, Species.BRONZOR, Species.AGGRON],
CANDICE: [Species.SNEASEL, Species.SNOVER, Species.SNORUNT],
VOLKNER: [Species.SHINX, Species.CHINCHOU, Species.ROTOM],
// Gym Leaders- Unova
CILAN: [Species.PANSAGE, Species.FOONGUS, Species.PETILIL],
CHILI: [Species.PANSEAR, Species.DARUMAKA, Species.NUMEL],
CRESS: [Species.PANPOUR, Species.TYMPOLE, Species.SLOWPOKE],
CHEREN: [Species.LILLIPUP, Species.MINCCINO, Species.PIDOVE],
LENORA: [Species.PATRAT, Species.DEERLING, Species.AUDINO],
ROXIE: [Species.VENIPEDE, Species.TRUBBISH, Species.SKORUPI],
BURGH: [Species.SEWADDLE, Species.SHELMET, Species.KARRABLAST],
ELESA: [Species.EMOLGA, Species.BLITZLE, Species.JOLTIK],
CLAY: [Species.DRILBUR, Species.SANDILE, Species.GOLETT],
SKYLA: [Species.DUCKLETT, Species.WOOBAT, Species.RUFFLET],
BRYCEN: [Species.CRYOGONAL, Species.VANILLITE, Species.CUBCHOO],
DRAYDEN: [Species.DRUDDIGON, Species.AXEW, Species.DEINO],
MARLON: [Species.WAILMER, Species.FRILLISH, Species.TIRTOUGA],
// Gym Leaders- Kalos
VIOLA: [Species.SURSKIT, Species.SCATTERBUG],
GRANT: [Species.AMAURA, Species.TYRUNT],
KORRINA: [Species.HAWLUCHA, Species.LUCARIO, Species.MIENFOO],
RAMOS: [Species.SKIDDO, Species.HOPPIP, Species.BELLSPROUT],
CLEMONT: [Species.HELIOPTILE, Species.MAGNEMITE, Species.EMOLGA],
VALERIE: [Species.SYLVEON, Species.MAWILE, Species.MR_MIME],
OLYMPIA: [Species.ESPURR, Species.SIGILYPH, Species.SLOWKING],
WULFRIC: [Species.BERGMITE, Species.SNOVER, Species.CRYOGONAL],
// Gym Leaders- Galar
MILO: [Species.GOSSIFLEUR, Species.APPLIN, Species.BOUNSWEET],
NESSA: [Species.CHEWTLE, Species.ARROKUDA, Species.WIMPOD],
KABU: [Species.SIZZLIPEDE, Species.VULPIX, Species.TORKOAL],
BEA: [Species.GALAR_FARFETCHD, Species.MACHOP, Species.CLOBBOPUS],
ALLISTER: [Species.GALAR_YAMASK, Species.GALAR_CORSOLA, Species.GASTLY],
OPAL: [Species.MILCERY, Species.TOGETIC, Species.GALAR_WEEZING],
BEDE: [Species.HATENNA, Species.GALAR_PONYTA, Species.GARDEVOIR],
GORDIE: [Species.ROLYCOLY, Species.STONJOURNER, Species.BINACLE],
MELONY: [Species.SNOM, Species.GALAR_DARUMAKA, Species.GALAR_MR_MIME],
PIERS: [Species.GALAR_ZIGZAGOON, Species.SCRAGGY, Species.INKAY],
MARNIE: [Species.IMPIDIMP, Species.PURRLOIN, Species.MORPEKO],
RAIHAN: [Species.DURALUDON, Species.TURTONATOR, Species.GOOMY],
// Gym Leaders- Paldea; First slot is Tera
KATY: [Species.TEDDIURSA, Species.NYMBLE, Species.TAROUNTULA], // Tera Bug Teddiursa
BRASSIUS: [Species.SUDOWOODO, Species.BRAMBLIN, Species.SMOLIV], // Tera Grass Sudowoodo
IONO: [Species.MISDREAVUS, Species.TADBULB, Species.WATTREL], // Tera Ghost Misdreavus
KOFU: [Species.CRABRAWLER, Species.VELUZA, Species.WIGLETT, Species.WINGULL], // Tera Water Crabrawler
LARRY: [Species.STARLY, Species.DUNSPARCE, Species.LECHONK, Species.KOMALA], // Tera Normal Starly
RYME: [Species.TOXEL, Species.GREAVARD, Species.SHUPPET, Species.MIMIKYU], // Tera Ghost Toxel
TULIP: [Species.FLABEBE, Species.FLITTLE, Species.RALTS, Species.GIRAFARIG], // Tera Psychic Flabebe
GRUSHA: [Species.SWABLU, Species.CETODDLE, Species.CUBCHOO, Species.ALOLA_VULPIX], // Tera Ice Swablu
// Elite Four- Kanto
LORELEI: [
Species.JYNX,
[Species.SLOWBRO, Species.GALAR_SLOWBRO],
Species.LAPRAS,
[Species.CLOYSTER, Species.ALOLA_SANDSLASH],
],
BRUNO: [Species.MACHAMP, Species.HITMONCHAN, Species.HITMONLEE, [Species.GOLEM, Species.ALOLA_GOLEM]],
AGATHA: [Species.GENGAR, [Species.ARBOK, Species.WEEZING], Species.CROBAT, Species.ALOLA_MAROWAK],
LANCE: [Species.DRAGONITE, Species.GYARADOS, Species.AERODACTYL, Species.ALOLA_EXEGGUTOR],
// Elite Four- Johto (Bruno included)
WILL: [Species.XATU, Species.JYNX, [Species.SLOWBRO, Species.SLOWKING], Species.EXEGGUTOR],
KOGA: [[Species.MUK, Species.WEEZING], [Species.VENOMOTH, Species.ARIADOS], Species.CROBAT, Species.TENTACRUEL],
KAREN: [Species.UMBREON, Species.HONCHKROW, Species.HOUNDOOM, Species.WEAVILE],
// Elite Four- Hoenn
SIDNEY: [
[Species.SHIFTRY, Species.CACTURNE],
[Species.SHARPEDO, Species.CRAWDAUNT],
Species.ABSOL,
Species.MIGHTYENA,
],
PHOEBE: [Species.SABLEYE, Species.DUSKNOIR, Species.BANETTE, [Species.DRIFBLIM, Species.MISMAGIUS]],
GLACIA: [Species.GLALIE, Species.WALREIN, Species.FROSLASS, Species.ABOMASNOW],
DRAKE: [Species.ALTARIA, Species.SALAMENCE, Species.FLYGON, Species.KINGDRA],
// Elite Four- Sinnoh
AARON: [[Species.SCIZOR, Species.KLEAVOR], Species.HERACROSS, [Species.VESPIQUEN, Species.YANMEGA], Species.DRAPION],
BERTHA: [Species.WHISCASH, Species.HIPPOWDON, Species.GLISCOR, Species.RHYPERIOR],
FLINT: [
[Species.RAPIDASH, Species.FLAREON],
Species.MAGMORTAR,
[Species.STEELIX, Species.LOPUNNY],
Species.INFERNAPE,
], // Tera Fire Steelix or Lopunny
LUCIAN: [Species.MR_MIME, Species.GALLADE, Species.BRONZONG, [Species.ALAKAZAM, Species.ESPEON]],
// Elite Four- Unova
SHAUNTAL: [Species.COFAGRIGUS, Species.CHANDELURE, Species.GOLURK, Species.JELLICENT],
MARSHAL: [Species.CONKELDURR, Species.MIENSHAO, Species.THROH, Species.SAWK],
GRIMSLEY: [Species.LIEPARD, Species.KINGAMBIT, Species.SCRAFTY, Species.KROOKODILE],
CAITLIN: [Species.MUSHARNA, Species.GOTHITELLE, Species.SIGILYPH, Species.REUNICLUS],
// Elite Four- Kalos
MALVA: [Species.PYROAR, Species.TORKOAL, Species.CHANDELURE, Species.TALONFLAME],
SIEBOLD: [Species.CLAWITZER, Species.GYARADOS, Species.BARBARACLE, Species.STARMIE],
WIKSTROM: [Species.KLEFKI, Species.PROBOPASS, Species.SCIZOR, Species.AEGISLASH],
DRASNA: [Species.DRAGALGE, Species.DRUDDIGON, Species.ALTARIA, Species.NOIVERN],
// Elite Four- Alola
HALA: [Species.HARIYAMA, Species.BEWEAR, Species.CRABOMINABLE, [Species.POLIWRATH, Species.ANNIHILAPE]],
MOLAYNE: [Species.KLEFKI, Species.MAGNEZONE, Species.METAGROSS, Species.ALOLA_DUGTRIO],
OLIVIA: [Species.RELICANTH, Species.CARBINK, Species.ALOLA_GOLEM, Species.LYCANROC],
ACEROLA: [[Species.BANETTE, Species.DRIFBLIM], Species.MIMIKYU, Species.DHELMISE, Species.PALOSSAND],
KAHILI: [[Species.BRAVIARY, Species.MANDIBUZZ], Species.HAWLUCHA, Species.ORICORIO, Species.TOUCANNON],
// Elite Four- Galar
MARNIE_ELITE: [Species.MORPEKO, Species.LIEPARD, [Species.TOXICROAK, Species.SCRAFTY], Species.GRIMMSNARL],
NESSA_ELITE: [Species.GOLISOPOD, [Species.QUAGSIRE, Species.PELIPPER], Species.TOXAPEX, Species.DREDNAW],
BEA_ELITE: [Species.HAWLUCHA, [Species.GRAPPLOCT, Species.SIRFETCHD], Species.FALINKS, Species.MACHAMP],
ALLISTER_ELITE: [Species.DUSKNOIR, [Species.POLTEAGEIST, Species.RUNERIGUS], Species.CURSOLA, Species.GENGAR],
RAIHAN_ELITE: [Species.GOODRA, [Species.TORKOAL, Species.TURTONATOR], Species.FLYGON, Species.ARCHALUDON],
// Elite Four- Paldea
RIKA: [Species.CLODSIRE, [Species.DUGTRIO, Species.DONPHAN], Species.CAMERUPT, Species.WHISCASH], // Tera Ground Clodsire
POPPY: [Species.TINKATON, Species.BRONZONG, Species.CORVIKNIGHT, Species.COPPERAJAH], // Tera Steel Tinkaton
LARRY_ELITE: [Species.FLAMIGO, Species.STARAPTOR, [Species.ALTARIA, Species.TROPIUS], Species.ORICORIO], // Tera Flying Flamigo; random Oricorio
HASSEL: [Species.BAXCALIBUR, [Species.FLAPPLE, Species.APPLETUN], Species.DRAGALGE, Species.NOIVERN], // Tera Dragon Baxcalibur
// Elite Four- BBL
CRISPIN: [Species.BLAZIKEN, Species.MAGMORTAR, [Species.CAMERUPT, Species.TALONFLAME], Species.ROTOM], // Tera Fire Blaziken; Heat Rotom
AMARYS: [Species.METAGROSS, Species.SCIZOR, Species.EMPOLEON, Species.SKARMORY], // Tera Steel Metagross
LACEY: [Species.EXCADRILL, Species.PRIMARINA, [Species.WHIMSICOTT, Species.ALCREMIE], Species.GRANBULL], // Tera Fairy Excadrill
DRAYTON: [Species.ARCHALUDON, Species.DRAGONITE, Species.HAXORUS, Species.SCEPTILE], // Tera Dragon Archaludon
};

View File

@ -1,20 +1,8 @@
import { globalScene } from "#app/global-scene";
import {
AttackMove,
BeakBlastHeaderAttr,
DelayedAttackAttr,
MoveFlags,
SelfStatusMove,
allMoves,
} from "./move";
import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove, allMoves } from "./moves/move";
import { MoveFlags } from "#enums/MoveFlags";
import type Pokemon from "../field/pokemon";
import {
type nil,
getFrameMs,
getEnumKeys,
getEnumValues,
animationFileName,
} from "../utils";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils";
import type { BattlerIndex } from "../battle";
import type { Element } from "json-stable-stringify";
import { Moves } from "#enums/moves";
@ -26,20 +14,20 @@ import { EncounterAnim } from "#enums/encounter-anims";
export enum AnimFrameTarget {
USER,
TARGET,
GRAPHIC
GRAPHIC,
}
enum AnimFocus {
TARGET = 1,
USER,
USER_TARGET,
SCREEN
SCREEN,
}
enum AnimBlendType {
NORMAL,
ADD,
SUBTRACT
SUBTRACT,
}
export enum ChargeAnim {
@ -63,7 +51,7 @@ export enum ChargeAnim {
SOLAR_BLADE_CHARGING,
BEAK_BLAST_CHARGING,
METEOR_BEAM_CHARGING,
ELECTRO_SHOT_CHARGING
ELECTRO_SHOT_CHARGING,
}
export enum CommonAnim {
@ -116,7 +104,7 @@ export enum CommonAnim {
ELECTRIC_TERRAIN,
GRASSY_TERRAIN,
PSYCHIC_TERRAIN,
LOCK_ON = 2120
LOCK_ON = 2120,
}
export class AnimConfig {
@ -128,7 +116,7 @@ export class AnimConfig {
public hue: number;
constructor(source?: any) {
this.frameTimedEvents = new Map<number, AnimTimedEvent[]>;
this.frameTimedEvents = new Map<number, AnimTimedEvent[]>();
if (source) {
this.id = source.id;
@ -160,7 +148,7 @@ export class AnimConfig {
timedEvent && timedEvents.push(timedEvent);
}
this.frameTimedEvents.set(parseInt(fte), timedEvents);
this.frameTimedEvents.set(Number.parseInt(fte), timedEvents);
}
this.position = source.position;
@ -218,9 +206,34 @@ class AnimFrame {
public priority: number;
public focus: AnimFocus;
constructor(x: number, y: number, zoomX: number, zoomY: number, angle: number, mirror: boolean, visible: boolean, blendType: AnimBlendType, pattern: number,
opacity: number, colorR: number, colorG: number, colorB: number, colorA: number, toneR: number, toneG: number, toneB: number, toneA: number,
flashR: number, flashG: number, flashB: number, flashA: number, locked: boolean, priority: number, focus: AnimFocus, init?: boolean) {
constructor(
x: number,
y: number,
zoomX: number,
zoomY: number,
angle: number,
mirror: boolean,
visible: boolean,
blendType: AnimBlendType,
pattern: number,
opacity: number,
colorR: number,
colorG: number,
colorB: number,
colorA: number,
toneR: number,
toneG: number,
toneB: number,
toneA: number,
flashR: number,
flashG: number,
flashB: number,
flashA: number,
locked: boolean,
priority: number,
focus: AnimFocus,
init?: boolean,
) {
this.x = !init ? ((x || 0) - 128) * 0.5 : x;
this.y = !init ? ((y || 0) - 224) * 0.5 : y;
if (zoomX) {
@ -305,7 +318,34 @@ class ImportedAnimFrame extends AnimFrame {
const color: number[] = source.color || [0, 0, 0, 0];
const tone: number[] = source.tone || [0, 0, 0, 0];
const flash: number[] = source.flash || [0, 0, 0, 0];
super(source.x, source.y, source.zoomX, source.zoomY, source.angle, source.mirror, source.visible, source.blendType, source.graphicFrame, source.opacity, color[0], color[1], color[2], color[3], tone[0], tone[1], tone[2], tone[3], flash[0], flash[1], flash[2], flash[3], source.locked, source.priority, source.focus, true);
super(
source.x,
source.y,
source.zoomX,
source.zoomY,
source.angle,
source.mirror,
source.visible,
source.blendType,
source.graphicFrame,
source.opacity,
color[0],
color[1],
color[2],
color[3],
tone[0],
tone[1],
tone[2],
tone[3],
flash[0],
flash[1],
flash[2],
flash[3],
source.locked,
source.priority,
source.focus,
true,
);
this.target = source.target;
this.graphicFrame = source.graphicFrame;
}
@ -326,8 +366,8 @@ abstract class AnimTimedEvent {
}
class AnimTimedSoundEvent extends AnimTimedEvent {
public volume: number = 100;
public pitch: number = 100;
public volume = 100;
public pitch = 100;
constructor(frameIndex: number, resourceName: string, source?: any) {
super(frameIndex, resourceName);
@ -338,8 +378,8 @@ class AnimTimedSoundEvent extends AnimTimedEvent {
}
}
execute(battleAnim: BattleAnim, priority?: number): number {
const soundConfig = { rate: (this.pitch * 0.01), volume: (this.volume * 0.01) };
execute(battleAnim: BattleAnim): number {
const soundConfig = { rate: this.pitch * 0.01, volume: this.volume * 0.01 };
if (this.resourceName) {
try {
globalScene.playSound(`battle_anims/${this.resourceName}`, soundConfig);
@ -347,9 +387,8 @@ class AnimTimedSoundEvent extends AnimTimedEvent {
console.error(err);
}
return Math.ceil((globalScene.sound.get(`battle_anims/${this.resourceName}`).totalDuration * 1000) / 33.33);
} else {
return Math.ceil((battleAnim.user!.cry(soundConfig).totalDuration * 1000) / 33.33); // TODO: is the bang behind user correct?
}
return Math.ceil((battleAnim.user!.cry(soundConfig).totalDuration * 1000) / 33.33); // TODO: is the bang behind user correct?
}
getEventType(): string {
@ -358,14 +397,14 @@ class AnimTimedSoundEvent extends AnimTimedEvent {
}
abstract class AnimTimedBgEvent extends AnimTimedEvent {
public bgX: number = 0;
public bgY: number = 0;
public opacity: number = 0;
public bgX = 0;
public bgY = 0;
public opacity = 0;
/*public colorRed: number = 0;
public colorGreen: number = 0;
public colorBlue: number = 0;
public colorAlpha: number = 0;*/
public duration: number = 0;
public duration = 0;
/*public flashScope: number = 0;
public flashRed: number = 0;
public flashGreen: number = 0;
@ -373,7 +412,7 @@ abstract class AnimTimedBgEvent extends AnimTimedEvent {
public flashAlpha: number = 0;
public flashDuration: number = 0;*/
constructor(frameIndex: number, resourceName: string, source: any) {
constructor(frameIndex: number, resourceName: string, source?: any) {
super(frameIndex, resourceName);
if (source) {
@ -396,26 +435,28 @@ abstract class AnimTimedBgEvent extends AnimTimedEvent {
}
class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
constructor(frameIndex: number, resourceName: string, source?: any) {
super(frameIndex, resourceName, source);
}
// biome-ignore lint/correctness/noUnusedVariables: seems intentional
execute(moveAnim: MoveAnim, priority?: number): number {
const tweenProps = {};
if (this.bgX !== undefined) {
tweenProps["x"] = (this.bgX * 0.5) - 320;
tweenProps["x"] = this.bgX * 0.5 - 320;
}
if (this.bgY !== undefined) {
tweenProps["y"] = (this.bgY * 0.5) - 284;
tweenProps["y"] = this.bgY * 0.5 - 284;
}
if (this.opacity !== undefined) {
tweenProps["alpha"] = (this.opacity || 0) / 255;
}
if (Object.keys(tweenProps).length) {
globalScene.tweens.add(Object.assign({
globalScene.tweens.add(
Object.assign(
{
targets: moveAnim.bgSprite,
duration: getFrameMs(this.duration * 3)
}, tweenProps));
duration: getFrameMs(this.duration * 3),
},
tweenProps,
),
);
}
return this.duration * 2;
}
@ -426,10 +467,6 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
}
class AnimTimedAddBgEvent extends AnimTimedBgEvent {
constructor(frameIndex: number, resourceName: string, source?: any) {
super(frameIndex, resourceName, source);
}
execute(moveAnim: MoveAnim, priority?: number): number {
if (moveAnim.bgSprite) {
moveAnim.bgSprite.destroy();
@ -450,7 +487,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
globalScene.tweens.add({
targets: moveAnim.bgSprite,
duration: getFrameMs(this.duration * 3)
duration: getFrameMs(this.duration * 3),
});
return this.duration * 2;
@ -473,9 +510,12 @@ export function initCommonAnims(): Promise<void> {
const commonAnimFetches: Promise<Map<CommonAnim, AnimConfig>>[] = [];
for (let ca = 0; ca < commonAnimIds.length; ca++) {
const commonAnimId = commonAnimIds[ca];
commonAnimFetches.push(globalScene.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, "-")}.json`)
commonAnimFetches.push(
globalScene
.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, "-")}.json`)
.then(response => response.json())
.then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))));
.then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))),
);
}
Promise.allSettled(commonAnimFetches).then(() => resolve());
});
@ -489,10 +529,9 @@ export function initMoveAnim(move: Moves): Promise<void> {
} else {
const loadedCheckTimer = setInterval(() => {
if (moveAnims.get(move) !== null) {
const chargeAnimSource = (allMoves[move].isChargingMove())
const chargeAnimSource = allMoves[move].isChargingMove()
? allMoves[move]
: (allMoves[move].getAttrs(DelayedAttackAttr)[0]
?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]);
: (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]);
if (chargeAnimSource && chargeAnims.get(chargeAnimSource.chargeAnim) === null) {
return;
}
@ -503,10 +542,16 @@ export function initMoveAnim(move: Moves): Promise<void> {
}
} else {
moveAnims.set(move, null);
const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP;
const defaultMoveAnim =
allMoves[move] instanceof AttackMove
? Moves.TACKLE
: allMoves[move] instanceof SelfStatusMove
? Moves.FOCUS_ENERGY
: Moves.TAIL_WHIP;
const fetchAnimAndResolve = (move: Moves) => {
globalScene.cachedFetch(`./battle-anims/${animationFileName(move)}.json`)
globalScene
.cachedFetch(`./battle-anims/${animationFileName(move)}.json`)
.then(response => {
const contentType = response.headers.get("content-type");
if (!response.ok || contentType?.indexOf("application/json") === -1) {
@ -523,10 +568,9 @@ export function initMoveAnim(move: Moves): Promise<void> {
} else {
populateMoveAnim(move, ba);
}
const chargeAnimSource = (allMoves[move].isChargingMove())
const chargeAnimSource = allMoves[move].isChargingMove()
? allMoves[move]
: (allMoves[move].getAttrs(DelayedAttackAttr)[0]
?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]);
: (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]);
if (chargeAnimSource) {
initMoveChargeAnim(chargeAnimSource.chargeAnim).then(() => resolve());
} else {
@ -579,9 +623,12 @@ export async function initEncounterAnims(encounterAnim: EncounterAnim | Encounte
if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) {
continue;
}
encounterAnimFetches.push(globalScene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`)
encounterAnimFetches.push(
globalScene
.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`)
.then(response => response.json())
.then(cas => encounterAnims.set(anim, new AnimConfig(cas))));
.then(cas => encounterAnims.set(anim, new AnimConfig(cas))),
);
}
await Promise.allSettled(encounterAnimFetches);
}
@ -601,7 +648,8 @@ export function initMoveChargeAnim(chargeAnim: ChargeAnim): Promise<void> {
}
} else {
chargeAnims.set(chargeAnim, null);
globalScene.cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/\_/g, "-")}.json`)
globalScene
.cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/\_/g, "-")}.json`)
.then(response => response.json())
.then(ca => {
if (Array.isArray(ca)) {
@ -651,12 +699,11 @@ export async function loadEncounterAnimAssets(startLoad?: boolean): Promise<void
export function loadMoveAnimAssets(moveIds: Moves[], startLoad?: boolean): Promise<void> {
return new Promise(resolve => {
const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat();
const moveAnimations = moveIds.flatMap(m => moveAnims.get(m) as AnimConfig);
for (const moveId of moveIds) {
const chargeAnimSource = (allMoves[moveId].isChargingMove())
const chargeAnimSource = allMoves[moveId].isChargingMove()
? allMoves[moveId]
: (allMoves[moveId].getAttrs(DelayedAttackAttr)[0]
?? allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]);
: (allMoves[moveId].getAttrs(DelayedAttackAttr)[0] ?? allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]);
if (chargeAnimSource) {
const moveChargeAnims = chargeAnims.get(chargeAnimSource.chargeAnim);
moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct?
@ -707,11 +754,11 @@ function loadAnimAssets(anims: AnimConfig[], startLoad?: boolean): Promise<void>
}
interface GraphicFrameData {
x: number,
y: number,
scaleX: number,
scaleY: number,
angle: number
x: number;
y: number;
scaleX: number;
scaleY: number;
angle: number;
}
const userFocusX = 106;
@ -719,12 +766,30 @@ const userFocusY = 148 - 32;
const targetFocusX = 234;
const targetFocusY = 84 - 32;
function transformPoint(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, px: number, py: number): [ x: number, y: number ] {
function transformPoint(
x1: number,
y1: number,
x2: number,
y2: number,
x3: number,
y3: number,
x4: number,
y4: number,
px: number,
py: number,
): [x: number, y: number] {
const yIntersect = yAxisIntersect(x1, y1, x2, y2, px, py);
return repositionY(x3, y3, x4, y4, yIntersect[0], yIntersect[1]);
}
function yAxisIntersect(x1: number, y1: number, x2: number, y2: number, px: number, py: number): [ x: number, y: number ] {
function yAxisIntersect(
x1: number,
y1: number,
x2: number,
y2: number,
px: number,
py: number,
): [x: number, y: number] {
const dx = x2 - x1;
const dy = y2 - y1;
const x = dx === 0 ? 0 : (px - x1) / dx;
@ -735,8 +800,8 @@ function yAxisIntersect(x1: number, y1: number, x2: number, y2: number, px: numb
function repositionY(x1: number, y1: number, x2: number, y2: number, tx: number, ty: number): [x: number, y: number] {
const dx = x2 - x1;
const dy = y2 - y1;
const x = x1 + (tx * dx);
const y = y1 + (ty * dy);
const x = x1 + tx * dx;
const y = y1 + ty * dy;
return [x, y];
}
@ -751,7 +816,7 @@ function isReversed(src1: number, src2: number, dst1: number, dst2: number) {
}
interface SpriteCache {
[key: number]: Phaser.GameObjects.Sprite[]
[key: number]: Phaser.GameObjects.Sprite[];
}
export abstract class BattleAnim {
@ -769,7 +834,7 @@ export abstract class BattleAnim {
private srcLine: number[];
private dstLine: number[];
constructor(user?: Pokemon, target?: Pokemon, playRegardlessOfIssues: boolean = false) {
constructor(user?: Pokemon, target?: Pokemon, playRegardlessOfIssues = false) {
this.user = user ?? null;
this.target = target ?? null;
this.sprites = [];
@ -788,18 +853,21 @@ export abstract class BattleAnim {
return false;
}
private getGraphicFrameData(frames: AnimFrame[], onSubstitute?: boolean): Map<number, Map<AnimFrameTarget, GraphicFrameData>> {
private getGraphicFrameData(
frames: AnimFrame[],
onSubstitute?: boolean,
): Map<number, Map<AnimFrameTarget, GraphicFrameData>> {
const ret: Map<number, Map<AnimFrameTarget, GraphicFrameData>> = new Map([
[AnimFrameTarget.GRAPHIC, new Map<AnimFrameTarget, GraphicFrameData>()],
[AnimFrameTarget.USER, new Map<AnimFrameTarget, GraphicFrameData>()],
[ AnimFrameTarget.TARGET, new Map<AnimFrameTarget, GraphicFrameData>() ]
[AnimFrameTarget.TARGET, new Map<AnimFrameTarget, GraphicFrameData>()],
]);
const isOppAnim = this.isOppAnim();
const user = !isOppAnim ? this.user : this.target;
const target = !isOppAnim ? this.target : this.user;
const targetSubstitute = (onSubstitute && user !== target) ? target!.getTag(SubstituteTag) : null;
const targetSubstitute = onSubstitute && user !== target ? target!.getTag(SubstituteTag) : null;
const userInitialX = user!.x; // TODO: is this bang correct?
const userInitialY = user!.y; // TODO: is this bang correct?
@ -817,24 +885,39 @@ export abstract class BattleAnim {
let x = frame.x + 106;
let y = frame.y + 116;
let scaleX = (frame.zoomX / 100) * (!frame.mirror ? 1 : -1);
const scaleY = (frame.zoomY / 100);
const scaleY = frame.zoomY / 100;
switch (frame.focus) {
case AnimFocus.TARGET:
x += targetInitialX - targetFocusX;
y += (targetInitialY - targetHalfHeight) - targetFocusY;
y += targetInitialY - targetHalfHeight - targetFocusY;
break;
case AnimFocus.USER:
x += userInitialX - userFocusX;
y += (userInitialY - userHalfHeight) - userFocusY;
y += userInitialY - userHalfHeight - userFocusY;
break;
case AnimFocus.USER_TARGET:
const point = transformPoint(this.srcLine[0], this.srcLine[1], this.srcLine[2], this.srcLine[3],
this.dstLine[0], this.dstLine[1] - userHalfHeight, this.dstLine[2], this.dstLine[3] - targetHalfHeight, x, y);
{
const point = transformPoint(
this.srcLine[0],
this.srcLine[1],
this.srcLine[2],
this.srcLine[3],
this.dstLine[0],
this.dstLine[1] - userHalfHeight,
this.dstLine[2],
this.dstLine[3] - targetHalfHeight,
x,
y,
);
x = point[0];
y = point[1];
if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) {
if (
frame.target === AnimFrameTarget.GRAPHIC &&
isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])
) {
scaleX = scaleX * -1;
}
}
break;
}
const angle = -frame.angle;
@ -845,6 +928,7 @@ export abstract class BattleAnim {
return ret;
}
// biome-ignore lint/complexity/noBannedTypes: callback is used liberally
play(onSubstitute?: boolean, callback?: Function) {
const isOppAnim = this.isOppAnim();
const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct?
@ -857,7 +941,7 @@ export abstract class BattleAnim {
return;
}
const targetSubstitute = (!!onSubstitute && user !== target) ? target.getTag(SubstituteTag) : null;
const targetSubstitute = !!onSubstitute && user !== target ? target.getTag(SubstituteTag) : null;
const userSprite = user.getSprite();
const targetSprite = targetSubstitute?.sprite ?? target.getSprite();
@ -865,7 +949,7 @@ export abstract class BattleAnim {
const spriteCache: SpriteCache = {
[AnimFrameTarget.GRAPHIC]: [],
[AnimFrameTarget.USER]: [],
[AnimFrameTarget.TARGET]: []
[AnimFrameTarget.TARGET]: [],
};
const spritePriorities: number[] = [];
@ -882,7 +966,7 @@ export abstract class BattleAnim {
} else {
targetSprite.setPosition(
target.x - target.getSubstituteOffset()[0],
target.y - target.getSubstituteOffset()[1]
target.y - target.getSubstituteOffset()[1],
);
targetSprite.setScale(target.getSpriteScale() * (target.isPlayer() ? 0.5 : 1));
targetSprite.setAlpha(1);
@ -953,15 +1037,25 @@ export abstract class BattleAnim {
const isUser = frame.target === AnimFrameTarget.USER;
if (isUser && target === user) {
continue;
} else if (this.playRegardlessOfIssues && frame.target === AnimFrameTarget.TARGET && !target.isOnField()) {
}
if (this.playRegardlessOfIssues && frame.target === AnimFrameTarget.TARGET && !target.isOnField()) {
continue;
}
const sprites = spriteCache[isUser ? AnimFrameTarget.USER : AnimFrameTarget.TARGET];
const spriteSource = isUser ? userSprite : targetSprite;
if ((isUser ? u : t) === sprites.length) {
if (isUser || !targetSubstitute) {
const sprite = globalScene.addPokemonSprite(isUser ? user! : target, 0, 0, spriteSource!.texture, spriteSource!.frame.name, true); // TODO: are those bangs correct?
[ "spriteColors", "fusionSpriteColors" ].map(k => sprite.pipelineData[k] = (isUser ? user! : target).getSprite().pipelineData[k]); // TODO: are those bangs correct?
const sprite = globalScene.addPokemonSprite(
isUser ? user! : target,
0,
0,
spriteSource!.texture,
spriteSource!.frame.name,
true,
); // TODO: are those bangs correct?
["spriteColors", "fusionSpriteColors"].map(
k => (sprite.pipelineData[k] = (isUser ? user! : target).getSprite().pipelineData[k]),
); // TODO: are those bangs correct?
sprite.setPipelineData("spriteKey", (isUser ? user! : target).getBattleSpriteKey());
sprite.setPipelineData("shiny", (isUser ? user : target).shiny);
sprite.setPipelineData("variant", (isUser ? user : target).variant);
@ -980,20 +1074,33 @@ export abstract class BattleAnim {
const spriteIndex = isUser ? u++ : t++;
const pokemonSprite = sprites[spriteIndex];
const graphicFrameData = frameData.get(frame.target)!.get(spriteIndex)!; // TODO: are the bangs correct?
const spriteSourceScale = (isUser || !targetSubstitute)
const spriteSourceScale =
isUser || !targetSubstitute
? spriteSource.parentContainer.scale
: target.getSpriteScale() * (target.isPlayer() ? 0.5 : 1);
pokemonSprite.setPosition(graphicFrameData.x, graphicFrameData.y - ((spriteSource.height / 2) * (spriteSourceScale - 1)));
pokemonSprite.setPosition(
graphicFrameData.x,
graphicFrameData.y - (spriteSource.height / 2) * (spriteSourceScale - 1),
);
pokemonSprite.setAngle(graphicFrameData.angle);
pokemonSprite.setScale(graphicFrameData.scaleX * spriteSourceScale, graphicFrameData.scaleY * spriteSourceScale);
pokemonSprite.setScale(
graphicFrameData.scaleX * spriteSourceScale,
graphicFrameData.scaleY * spriteSourceScale,
);
pokemonSprite.setData("locked", frame.locked);
pokemonSprite.setAlpha(frame.opacity / 255);
pokemonSprite.pipelineData["tone"] = frame.tone;
pokemonSprite.setVisible(frame.visible && (isUser ? user.visible : target.visible));
pokemonSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
pokemonSprite.setBlendMode(
frame.blendType === AnimBlendType.NORMAL
? Phaser.BlendModes.NORMAL
: frame.blendType === AnimBlendType.ADD
? Phaser.BlendModes.ADD
: Phaser.BlendModes.DIFFERENCE,
);
} else {
const sprites = spriteCache[AnimFrameTarget.GRAPHIC];
if (g === sprites.length) {
@ -1019,10 +1126,12 @@ export abstract class BattleAnim {
/** The sprite we are moving the moveSprite in relation to */
let targetSprite: Phaser.GameObjects.GameObject | nil;
/** The method that is being used to move the sprite.*/
let moveFunc: ((sprite: Phaser.GameObjects.GameObject, target: Phaser.GameObjects.GameObject) => void) |
((sprite: Phaser.GameObjects.GameObject) => void) = globalScene.field.bringToTop;
let moveFunc:
| ((sprite: Phaser.GameObjects.GameObject, target: Phaser.GameObjects.GameObject) => void)
| ((sprite: Phaser.GameObjects.GameObject) => void) = globalScene.field.bringToTop;
if (priority === 0) { // Place the sprite in front of the pokemon on the field.
if (priority === 0) {
// Place the sprite in front of the pokemon on the field.
targetSprite = globalScene.getEnemyField().find(p => p) ?? globalScene.getPlayerField().find(p => p);
console.log(typeof targetSprite);
moveFunc = globalScene.field.moveBelow;
@ -1039,7 +1148,9 @@ export abstract class BattleAnim {
}
// If target sprite is not undefined and exists in the field container, then move the sprite using the moveFunc.
// Otherwise, default to just bringing it to the top.
targetSprite && globalScene.field.exists(targetSprite) ? moveFunc.bind(globalScene.field)(moveSprite as Phaser.GameObjects.GameObject, targetSprite) : globalScene.field.bringToTop(moveSprite as Phaser.GameObjects.GameObject);
targetSprite && globalScene.field.exists(targetSprite)
? moveFunc.bind(globalScene.field)(moveSprite as Phaser.GameObjects.GameObject, targetSprite)
: globalScene.field.bringToTop(moveSprite as Phaser.GameObjects.GameObject);
};
setSpritePriority(frame.priority);
}
@ -1053,7 +1164,13 @@ export abstract class BattleAnim {
moveSprite.setAlpha(frame.opacity / 255);
moveSprite.setVisible(frame.visible);
moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
moveSprite.setBlendMode(
frame.blendType === AnimBlendType.NORMAL
? Phaser.BlendModes.NORMAL
: frame.blendType === AnimBlendType.ADD
? Phaser.BlendModes.ADD
: Phaser.BlendModes.DIFFERENCE,
);
}
}
if (anim?.frameTimedEvents.has(f)) {
@ -1092,20 +1209,24 @@ export abstract class BattleAnim {
if (r) {
globalScene.tweens.addCounter({
duration: getFrameMs(r),
onComplete: () => cleanUpAndComplete()
onComplete: () => cleanUpAndComplete(),
});
} else {
cleanUpAndComplete();
}
}
},
});
}
private getGraphicFrameDataWithoutTarget(frames: AnimFrame[], targetInitialX: number, targetInitialY: number): Map<number, Map<AnimFrameTarget, GraphicFrameData>> {
private getGraphicFrameDataWithoutTarget(
frames: AnimFrame[],
targetInitialX: number,
targetInitialY: number,
): Map<number, Map<AnimFrameTarget, GraphicFrameData>> {
const ret: Map<number, Map<AnimFrameTarget, GraphicFrameData>> = new Map([
[AnimFrameTarget.GRAPHIC, new Map<AnimFrameTarget, GraphicFrameData>()],
[AnimFrameTarget.USER, new Map<AnimFrameTarget, GraphicFrameData>()],
[ AnimFrameTarget.TARGET, new Map<AnimFrameTarget, GraphicFrameData>() ]
[AnimFrameTarget.TARGET, new Map<AnimFrameTarget, GraphicFrameData>()],
]);
let g = 0;
@ -1115,12 +1236,18 @@ export abstract class BattleAnim {
for (const frame of frames) {
let { x, y } = frame;
const scaleX = (frame.zoomX / 100) * (!frame.mirror ? 1 : -1);
const scaleY = (frame.zoomY / 100);
const scaleY = frame.zoomY / 100;
x += targetInitialX;
y += targetInitialY;
const angle = -frame.angle;
const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++;
ret.get(frame.target)?.set(key, { x: x, y: y, scaleX: scaleX, scaleY: scaleY, angle: angle });
ret.get(frame.target)?.set(key, {
x: x,
y: y,
scaleX: scaleX,
scaleY: scaleY,
angle: angle,
});
}
return ret;
@ -1137,11 +1264,18 @@ export abstract class BattleAnim {
* - 5 is on top of player sprite
* @param callback
*/
playWithoutTargets(targetInitialX: number, targetInitialY: number, frameTimeMult: number, frameTimedEventPriority?: 0 | 1 | 3 | 5, callback?: Function) {
playWithoutTargets(
targetInitialX: number,
targetInitialY: number,
frameTimeMult: number,
frameTimedEventPriority?: 0 | 1 | 3 | 5,
// biome-ignore lint/complexity/noBannedTypes: callback is used liberally
callback?: Function,
) {
const spriteCache: SpriteCache = {
[AnimFrameTarget.GRAPHIC]: [],
[AnimFrameTarget.USER]: [],
[AnimFrameTarget.TARGET]: []
[AnimFrameTarget.TARGET]: [],
};
const cleanUpAndComplete = () => {
@ -1178,7 +1312,11 @@ export abstract class BattleAnim {
onRepeat: () => {
existingFieldSprites = globalScene.field.getAll().slice(0);
const spriteFrames = anim!.frames[frameCount];
const frameData = this.getGraphicFrameDataWithoutTarget(anim!.frames[frameCount], targetInitialX, targetInitialY);
const frameData = this.getGraphicFrameDataWithoutTarget(
anim!.frames[frameCount],
targetInitialX,
targetInitialY,
);
let graphicFrameCount = 0;
for (const frame of spriteFrames) {
if (frame.target !== AnimFrameTarget.GRAPHIC) {
@ -1218,7 +1356,13 @@ export abstract class BattleAnim {
moveSprite.setAlpha(frame.opacity / 255);
moveSprite.setVisible(frame.visible);
moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
moveSprite.setBlendMode(
frame.blendType === AnimBlendType.NORMAL
? Phaser.BlendModes.NORMAL
: frame.blendType === AnimBlendType.ADD
? Phaser.BlendModes.ADD
: Phaser.BlendModes.DIFFERENCE,
);
}
}
if (anim?.frameTimedEvents.get(frameCount)) {
@ -1253,12 +1397,12 @@ export abstract class BattleAnim {
if (totalFrames) {
globalScene.tweens.addCounter({
duration: getFrameMs(totalFrames),
onComplete: () => cleanUpAndComplete()
onComplete: () => cleanUpAndComplete(),
});
} else {
cleanUpAndComplete();
}
}
},
});
}
}
@ -1266,14 +1410,14 @@ export abstract class BattleAnim {
export class CommonBattleAnim extends BattleAnim {
public commonAnim: CommonAnim | null;
constructor(commonAnim: CommonAnim | null, user: Pokemon, target?: Pokemon, playOnEmptyField: boolean = false) {
constructor(commonAnim: CommonAnim | null, user: Pokemon, target?: Pokemon, playOnEmptyField = false) {
super(user, target || user, playOnEmptyField);
this.commonAnim = commonAnim;
}
getAnim(): AnimConfig | null {
return this.commonAnim ? commonAnims.get(this.commonAnim) ?? null : null;
return this.commonAnim ? (commonAnims.get(this.commonAnim) ?? null) : null;
}
isOppAnim(): boolean {
@ -1284,7 +1428,7 @@ export class CommonBattleAnim extends BattleAnim {
export class MoveAnim extends BattleAnim {
public move: Moves;
constructor(move: Moves, user: Pokemon, target: BattlerIndex, playOnEmptyField: boolean = false) {
constructor(move: Moves, user: Pokemon, target: BattlerIndex, playOnEmptyField = false) {
super(user, globalScene.getField()[target], playOnEmptyField);
this.move = move;
@ -1292,8 +1436,8 @@ export class MoveAnim extends BattleAnim {
getAnim(): AnimConfig {
return moveAnims.get(this.move) instanceof AnimConfig
? moveAnims.get(this.move) as AnimConfig
: moveAnims.get(this.move)?.[this.user?.isPlayer() ? 0 : 1] as AnimConfig;
? (moveAnims.get(this.move) as AnimConfig)
: (moveAnims.get(this.move)?.[this.user?.isPlayer() ? 0 : 1] as AnimConfig);
}
isOppAnim(): boolean {
@ -1324,8 +1468,8 @@ export class MoveChargeAnim extends MoveAnim {
getAnim(): AnimConfig {
return chargeAnims.get(this.chargeAnim) instanceof AnimConfig
? chargeAnims.get(this.chargeAnim) as AnimConfig
: chargeAnims.get(this.chargeAnim)?.[this.user?.isPlayer() ? 0 : 1] as AnimConfig;
? (chargeAnims.get(this.chargeAnim) as AnimConfig)
: (chargeAnims.get(this.chargeAnim)?.[this.user?.isPlayer() ? 0 : 1] as AnimConfig);
}
}
@ -1398,43 +1542,76 @@ export async function populateAnims() {
} else if (chargeAnimId) {
chargeAnims.set(chargeAnimId, !isOppMove ? anim : [chargeAnims.get(chargeAnimId) as AnimConfig, anim]);
} else {
moveAnims.set(moveNameToId[animName], !isOppMove ? anim as AnimConfig : [ moveAnims.get(moveNameToId[animName]) as AnimConfig, anim as AnimConfig ]);
moveAnims.set(
moveNameToId[animName],
!isOppMove ? (anim as AnimConfig) : [moveAnims.get(moveNameToId[animName]) as AnimConfig, anim as AnimConfig],
);
}
for (let f = 0; f < fields.length; f++) {
const field = fields[f];
const fieldName = field.slice(0, field.indexOf(":"));
const fieldData = field.slice(fieldName.length + 1, field.lastIndexOf("\n")).trim();
switch (fieldName) {
case "array":
case "array": {
const framesData = fieldData.split(" - - - ").slice(1);
for (let fd = 0; fd < framesData.length; fd++) {
anim.frames.push([]);
const frameData = framesData[fd];
const focusFramesData = frameData.split(" - - ");
for (let tf = 0; tf < focusFramesData.length; tf++) {
const values = focusFramesData[tf].replace(/ \- /g, "").split("\n");
const targetFrame = new AnimFrame(parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[11]), parseFloat(values[3]),
parseInt(values[4]) === 1, parseInt(values[6]) === 1, parseInt(values[5]), parseInt(values[7]), parseInt(values[8]), parseInt(values[12]), parseInt(values[13]),
parseInt(values[14]), parseInt(values[15]), parseInt(values[16]), parseInt(values[17]), parseInt(values[18]), parseInt(values[19]),
parseInt(values[21]), parseInt(values[22]), parseInt(values[23]), parseInt(values[24]), parseInt(values[20]) === 1, parseInt(values[25]), parseInt(values[26]) as AnimFocus);
const values = focusFramesData[tf].replace(/ {6}\- /g, "").split("\n");
const targetFrame = new AnimFrame(
Number.parseFloat(values[0]),
Number.parseFloat(values[1]),
Number.parseFloat(values[2]),
Number.parseFloat(values[11]),
Number.parseFloat(values[3]),
Number.parseInt(values[4]) === 1,
Number.parseInt(values[6]) === 1,
Number.parseInt(values[5]),
Number.parseInt(values[7]),
Number.parseInt(values[8]),
Number.parseInt(values[12]),
Number.parseInt(values[13]),
Number.parseInt(values[14]),
Number.parseInt(values[15]),
Number.parseInt(values[16]),
Number.parseInt(values[17]),
Number.parseInt(values[18]),
Number.parseInt(values[19]),
Number.parseInt(values[21]),
Number.parseInt(values[22]),
Number.parseInt(values[23]),
Number.parseInt(values[24]),
Number.parseInt(values[20]) === 1,
Number.parseInt(values[25]),
Number.parseInt(values[26]) as AnimFocus,
);
anim.frames[fd].push(targetFrame);
}
}
break;
case "graphic":
}
case "graphic": {
const graphic = fieldData !== "''" ? fieldData : "";
anim.graphic = graphic.indexOf(".") > -1
? graphic.slice(0, fieldData.indexOf("."))
: graphic;
anim.graphic = graphic.indexOf(".") > -1 ? graphic.slice(0, fieldData.indexOf(".")) : graphic;
break;
case "timing":
}
case "timing": {
const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1);
for (let t = 0; t < timingEntries.length; t++) {
const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",")
.replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1");
const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct?
const timingData = timingEntries[t]
.replace(/\n/g, " ")
.replace(/[ ]{2,}/g, " ")
.replace(/[a-z]+: ! '', /gi, "")
.replace(/name: (.*?),/, 'name: "$1",')
.replace(
/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/,
"flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1",
);
const frameIndex = Number.parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct?
let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct?
const timingType = parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct?
const timingType = Number.parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct?
let timedEvent: AnimTimedEvent | undefined;
switch (timingType) {
case 0:
@ -1464,15 +1641,16 @@ export async function populateAnims() {
if (!timedEvent) {
continue;
}
const propPattern = /([a-z]+): (.*?)(?:,|\})/ig;
const propPattern = /([a-z]+): (.*?)(?:,|\})/gi;
let propMatch: RegExpExecArray;
while ((propMatch = propPattern.exec(timingData)!)) { // TODO: is this bang correct?
while ((propMatch = propPattern.exec(timingData)!)) {
// TODO: is this bang correct?
const prop = propMatch[1];
let value: any = propMatch[2];
switch (prop) {
case "bgX":
case "bgY":
value = parseFloat(value);
value = Number.parseFloat(value);
break;
case "volume":
case "pitch":
@ -1488,7 +1666,7 @@ export async function populateAnims() {
case "flashBlue":
case "flashAlpha":
case "flashDuration":
value = parseInt(value);
value = Number.parseInt(value);
break;
}
if (timedEvent.hasOwnProperty(prop)) {
@ -1501,18 +1679,18 @@ export async function populateAnims() {
anim.frameTimedEvents.get(frameIndex)!.push(timedEvent); // TODO: is this bang correct?
}
break;
}
case "position":
anim.position = parseInt(fieldData);
anim.position = Number.parseInt(fieldData);
break;
case "hue":
anim.hue = parseInt(fieldData);
anim.hue = Number.parseInt(fieldData);
break;
}
}
}
// used in commented code
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// biome-ignore lint/correctness/noUnusedVariables: used in commented code
const animReplacer = (k, v) => {
if (k === "id" && !v) {
return undefined;
@ -1527,11 +1705,28 @@ export async function populateAnims() {
};
const animConfigProps = ["id", "graphic", "frames", "frameTimedEvents", "position", "hue"];
const animFrameProps = [ "x", "y", "zoomX", "zoomY", "angle", "mirror", "visible", "blendType", "target", "graphicFrame", "opacity", "color", "tone", "flash", "locked", "priority", "focus" ];
const animFrameProps = [
"x",
"y",
"zoomX",
"zoomY",
"angle",
"mirror",
"visible",
"blendType",
"target",
"graphicFrame",
"opacity",
"color",
"tone",
"flash",
"locked",
"priority",
"focus",
];
const propSets = [animConfigProps, animFrameProps];
// used in commented code
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// biome-ignore lint/correctness/noUnusedVariables: used in commented code
const animComparator = (a: Element, b: Element) => {
let props: string[];
for (let p = 0; p < propSets.length; p++) {

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,13 @@ import type Pokemon from "../field/pokemon";
import { HitResult } from "../field/pokemon";
import { getStatusEffectHealText } from "./status-effect";
import * as Utils from "../utils";
import { DoubleBerryEffectAbAttr, PostItemLostAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs, applyPostItemLostAbAttrs } from "./ability";
import {
DoubleBerryEffectAbAttr,
PostItemLostAbAttr,
ReduceBerryUseThresholdAbAttr,
applyAbAttrs,
applyPostItemLostAbAttrs,
} from "./ability";
import i18next from "i18next";
import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type";
@ -29,7 +35,8 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
case BerryType.LUM:
return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED);
case BerryType.ENIGMA:
return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length;
return (pokemon: Pokemon) =>
!!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length;
case BerryType.LIECHI:
case BerryType.GANLON:
case BerryType.PETAYA:
@ -75,8 +82,17 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
}
const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed);
globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(),
hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true));
globalScene.unshiftPhase(
new PokemonHealPhase(
pokemon.getBattlerIndex(),
hpHealed.value,
i18next.t("battle:hpHealBerry", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
berryName: getBerryName(berryType),
}),
true,
),
);
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false);
};
case BerryType.LUM:
@ -131,10 +147,18 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct?
const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio())
? pokemon.getMoveset().find(m => !m?.getPpRatio())
: pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct?
if (ppRestoreMove !== undefined) {
ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0);
globalScene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) }));
globalScene.queueMessage(
i18next.t("battle:ppHealBerry", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: ppRestoreMove!.getName(),
berryName: getBerryName(berryType),
}),
);
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false);
}
};

View File

@ -11,16 +11,17 @@ import type { FixedBattleConfig } from "#app/battle";
import { ClassicFixedBossWaves, BattleType, getRandomTrainerFunc } from "#app/battle";
import Trainer, { TrainerVariant } from "#app/field/trainer";
import type { GameMode } from "#app/game-mode";
import { Type } from "#enums/type";
import { PokemonType } from "#enums/pokemon-type";
import { Challenges } from "#enums/challenges";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import { Nature } from "#enums/nature";
import type { Moves } from "#enums/moves";
import { TypeColor, TypeShadow } from "#enums/color";
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { pokemonFormChanges } from "#app/data/pokemon-forms";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { globalScene } from "#app/global-scene";
import { pokemonFormChanges } from "./pokemon-forms";
import { pokemonEvolutions } from "./balance/pokemon-evolutions";
/** A constant for the default max cost of the starting party before a run */
const DEFAULT_PARTY_MAX_COST = 10;
@ -93,7 +94,6 @@ export enum ChallengeType {
* Modifies what the pokemon stats for Flip Stat Mode.
*/
FLIP_STAT,
}
/**
@ -106,7 +106,7 @@ export enum MoveSourceType {
GREAT_TM,
ULTRA_TM,
COMMON_EGG,
RARE_EGG
RARE_EGG,
}
/**
@ -148,7 +148,10 @@ export abstract class Challenge {
* @returns {@link string} The i18n key for this challenge
*/
geti18nKey(): string {
return Challenges[this.id].split("_").map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("");
return Challenges[this.id]
.split("_")
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("");
}
/**
@ -274,88 +277,87 @@ export abstract class Challenge {
* @param source The source challenge or json.
* @returns This challenge.
*/
static loadChallenge(source: Challenge | any): Challenge {
static loadChallenge(_source: Challenge | any): Challenge {
throw new Error("Method not implemented! Use derived class");
}
/**
* An apply function for STARTER_CHOICE challenges. Derived classes should alter this.
* @param pokemon {@link PokemonSpecies} The pokemon to check the validity of.
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon.
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @param _pokemon {@link PokemonSpecies} The pokemon to check the validity of.
* @param _valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @param _dexAttr {@link DexAttrProps} The dex attributes of the pokemon.
* @returns {@link boolean} Whether this function did anything.
*/
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
applyStarterChoice(_pokemon: PokemonSpecies, _valid: Utils.BooleanHolder, _dexAttr: DexAttrProps): boolean {
return false;
}
/**
* An apply function for STARTER_POINTS challenges. Derived classes should alter this.
* @param points {@link Utils.NumberHolder} The amount of points you have available.
* @param _points {@link Utils.NumberHolder} The amount of points you have available.
* @returns {@link boolean} Whether this function did anything.
*/
applyStarterPoints(points: Utils.NumberHolder): boolean {
applyStarterPoints(_points: Utils.NumberHolder): boolean {
return false;
}
/**
* An apply function for STARTER_COST challenges. Derived classes should alter this.
* @param species {@link Species} The pokemon to change the cost of.
* @param cost {@link Utils.NumberHolder} The cost of the starter.
* @param _species {@link Species} The pokemon to change the cost of.
* @param _cost {@link Utils.NumberHolder} The cost of the starter.
* @returns {@link boolean} Whether this function did anything.
*/
applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean {
applyStarterCost(_species: Species, _cost: Utils.NumberHolder): boolean {
return false;
}
/**
* An apply function for STARTER_MODIFY challenges. Derived classes should alter this.
* @param pokemon {@link Pokemon} The starter pokemon to modify.
* @param _pokemon {@link Pokemon} The starter pokemon to modify.
* @returns {@link boolean} Whether this function did anything.
*/
applyStarterModify(pokemon: Pokemon): boolean {
applyStarterModify(_pokemon: Pokemon): boolean {
return false;
}
/**
* An apply function for POKEMON_IN_BATTLE challenges. Derived classes should alter this.
* @param pokemon {@link Pokemon} The pokemon to check the validity of.
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @param _pokemon {@link Pokemon} The pokemon to check the validity of.
* @param _valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @returns {@link boolean} Whether this function did anything.
*/
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
applyPokemonInBattle(_pokemon: Pokemon, _valid: Utils.BooleanHolder): boolean {
return false;
}
/**
* An apply function for FIXED_BATTLE challenges. Derived classes should alter this.
* @param waveIndex {@link Number} The current wave index.
* @param battleConfig {@link FixedBattleConfig} The battle config to modify.
* @param _waveIndex {@link Number} The current wave index.
* @param _battleConfig {@link FixedBattleConfig} The battle config to modify.
* @returns {@link boolean} Whether this function did anything.
*/
applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean {
applyFixedBattle(_waveIndex: number, _battleConfig: FixedBattleConfig): boolean {
return false;
}
/**
* An apply function for TYPE_EFFECTIVENESS challenges. Derived classes should alter this.
* @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move.
* @param _effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move.
* @returns Whether this function did anything.
*/
applyTypeEffectiveness(effectiveness: Utils.NumberHolder): boolean {
applyTypeEffectiveness(_effectiveness: Utils.NumberHolder): boolean {
return false;
}
/**
* An apply function for AI_LEVEL challenges. Derived classes should alter this.
* @param level {@link Utils.NumberHolder} The generated level.
* @param levelCap {@link Number} The current level cap.
* @param isTrainer {@link Boolean} Whether this is a trainer pokemon.
* @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon.
* @param _level {@link Utils.NumberHolder} The generated level.
* @param _levelCap {@link Number} The current level cap.
* @param _isTrainer {@link Boolean} Whether this is a trainer pokemon.
* @param _isBoss {@link Boolean} Whether this is a non-trainer boss pokemon.
* @returns {@link boolean} Whether this function did anything.
*/
applyLevelChange(level: Utils.NumberHolder, levelCap: number, isTrainer: boolean, isBoss: boolean): boolean {
applyLevelChange(_level: Utils.NumberHolder, _levelCap: number, _isTrainer: boolean, _isBoss: boolean): boolean {
return false;
}
@ -365,7 +367,7 @@ export abstract class Challenge {
* @param moveSlots {@link Utils.NumberHolder} The amount of move slots.
* @returns {@link boolean} Whether this function did anything.
*/
applyMoveSlot(pokemon: Pokemon, moveSlots: Utils.NumberHolder): boolean {
applyMoveSlot(_pokemon: Pokemon, _moveSlots: Utils.NumberHolder): boolean {
return false;
}
@ -375,7 +377,7 @@ export abstract class Challenge {
* @param hasPassive {@link Utils.BooleanHolder} Whether it should have its passive.
* @returns {@link boolean} Whether this function did anything.
*/
applyPassiveAccess(pokemon: Pokemon, hasPassive: Utils.BooleanHolder): boolean {
applyPassiveAccess(_pokemon: Pokemon, _hasPassive: Utils.BooleanHolder): boolean {
return false;
}
@ -384,41 +386,46 @@ export abstract class Challenge {
* @param gameMode {@link GameMode} The current game mode.
* @returns {@link boolean} Whether this function did anything.
*/
applyGameModeModify(gameMode: GameMode): boolean {
applyGameModeModify(_gameMode: GameMode): boolean {
return false;
}
/**
* An apply function for MOVE_ACCESS. Derived classes should alter this.
* @param pokemon {@link Pokemon} What pokemon would learn the move.
* @param moveSource {@link MoveSourceType} What source the pokemon would get the move from.
* @param move {@link Moves} The move in question.
* @param level {@link Utils.NumberHolder} The level threshold for access.
* @param _pokemon {@link Pokemon} What pokemon would learn the move.
* @param _moveSource {@link MoveSourceType} What source the pokemon would get the move from.
* @param _move {@link Moves} The move in question.
* @param _level {@link Utils.NumberHolder} The level threshold for access.
* @returns {@link boolean} Whether this function did anything.
*/
applyMoveAccessLevel(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.NumberHolder): boolean {
applyMoveAccessLevel(
_pokemon: Pokemon,
_moveSource: MoveSourceType,
_move: Moves,
_level: Utils.NumberHolder,
): boolean {
return false;
}
/**
* An apply function for MOVE_WEIGHT. Derived classes should alter this.
* @param pokemon {@link Pokemon} What pokemon would learn the move.
* @param moveSource {@link MoveSourceType} What source the pokemon would get the move from.
* @param move {@link Moves} The move in question.
* @param weight {@link Utils.NumberHolder} The base weight of the move
* @param _pokemon {@link Pokemon} What pokemon would learn the move.
* @param _moveSource {@link MoveSourceType} What source the pokemon would get the move from.
* @param _move {@link Moves} The move in question.
* @param _weight {@link Utils.NumberHolder} The base weight of the move
* @returns {@link boolean} Whether this function did anything.
*/
applyMoveWeight(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.NumberHolder): boolean {
applyMoveWeight(_pokemon: Pokemon, _moveSource: MoveSourceType, _move: Moves, _level: Utils.NumberHolder): boolean {
return false;
}
/**
* An apply function for FlipStats. Derived classes should alter this.
* @param pokemon {@link Pokemon} What pokemon would learn the move.
* @param baseStats What are the stats to flip.
* @param _pokemon {@link Pokemon} What pokemon would learn the move.
* @param _baseStats What are the stats to flip.
* @returns {@link boolean} Whether this function did anything.
*/
applyFlipStat(pokemon: Pokemon, baseStats: number[]) {
applyFlipStat(_pokemon: Pokemon, _baseStats: number[]) {
return false;
}
}
@ -433,22 +440,8 @@ export class SingleGenerationChallenge extends Challenge {
super(Challenges.SINGLE_GENERATION, 9);
}
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
const generations = [ pokemon.generation ];
if (soft) {
const speciesToCheck = [ pokemon.speciesId ];
while (speciesToCheck.length) {
const checking = speciesToCheck.pop();
if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId);
generations.push(getPokemonSpecies(e.speciesId).generation);
});
}
}
}
if (!generations.includes(this.value)) {
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean {
if (pokemon.generation !== this.value) {
valid.value = false;
return true;
}
@ -458,7 +451,10 @@ export class SingleGenerationChallenge extends Challenge {
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
const baseGeneration = getPokemonSpecies(pokemon.species.speciesId).generation;
const fusionGeneration = pokemon.isFusion() ? getPokemonSpecies(pokemon.fusionSpecies!.speciesId).generation : 0; // TODO: is the bang on fusionSpecies correct?
if (pokemon.isPlayer() && (baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))) {
if (
pokemon.isPlayer() &&
(baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))
) {
valid.value = false;
return true;
}
@ -467,11 +463,63 @@ export class SingleGenerationChallenge extends Challenge {
applyFixedBattle(waveIndex: number, battleConfig: FixedBattleConfig): boolean {
let trainerTypes: (TrainerType | TrainerType[])[] = [];
const evilTeamWaves: number[] = [ ClassicFixedBossWaves.EVIL_GRUNT_1, ClassicFixedBossWaves.EVIL_GRUNT_2, ClassicFixedBossWaves.EVIL_GRUNT_3, ClassicFixedBossWaves.EVIL_ADMIN_1, ClassicFixedBossWaves.EVIL_GRUNT_4, ClassicFixedBossWaves.EVIL_ADMIN_2, ClassicFixedBossWaves.EVIL_BOSS_1, ClassicFixedBossWaves.EVIL_BOSS_2 ];
const evilTeamGrunts = [[ TrainerType.ROCKET_GRUNT ], [ TrainerType.ROCKET_GRUNT ], [ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ], [ TrainerType.GALACTIC_GRUNT ], [ TrainerType.PLASMA_GRUNT ], [ TrainerType.FLARE_GRUNT ], [ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ], [ TrainerType.MACRO_GRUNT ], [ TrainerType.STAR_GRUNT ]];
const evilTeamAdmins = [[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [[ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ]], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.COLRESS ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], [ TrainerType.FABA, TrainerType.PLUMERIA ], [ TrainerType.OLEANA ], [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]];
const evilTeamBosses = [[ TrainerType.ROCKET_BOSS_GIOVANNI_1 ], [ TrainerType.ROCKET_BOSS_GIOVANNI_1 ], [ TrainerType.MAXIE, TrainerType.ARCHIE ], [ TrainerType.CYRUS ], [ TrainerType.GHETSIS ], [ TrainerType.LYSANDRE ], [ TrainerType.LUSAMINE, TrainerType.GUZMA ], [ TrainerType.ROSE ], [ TrainerType.PENNY ]];
const evilTeamBossRematches = [[ TrainerType.ROCKET_BOSS_GIOVANNI_2 ], [ TrainerType.ROCKET_BOSS_GIOVANNI_2 ], [ TrainerType.MAXIE_2, TrainerType.ARCHIE_2 ], [ TrainerType.CYRUS_2 ], [ TrainerType.GHETSIS_2 ], [ TrainerType.LYSANDRE_2 ], [ TrainerType.LUSAMINE_2, TrainerType.GUZMA_2 ], [ TrainerType.ROSE_2 ], [ TrainerType.PENNY_2 ]];
const evilTeamWaves: number[] = [
ClassicFixedBossWaves.EVIL_GRUNT_1,
ClassicFixedBossWaves.EVIL_GRUNT_2,
ClassicFixedBossWaves.EVIL_GRUNT_3,
ClassicFixedBossWaves.EVIL_ADMIN_1,
ClassicFixedBossWaves.EVIL_GRUNT_4,
ClassicFixedBossWaves.EVIL_ADMIN_2,
ClassicFixedBossWaves.EVIL_BOSS_1,
ClassicFixedBossWaves.EVIL_BOSS_2,
];
const evilTeamGrunts = [
[TrainerType.ROCKET_GRUNT],
[TrainerType.ROCKET_GRUNT],
[TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT],
[TrainerType.GALACTIC_GRUNT],
[TrainerType.PLASMA_GRUNT],
[TrainerType.FLARE_GRUNT],
[TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT],
[TrainerType.MACRO_GRUNT],
[TrainerType.STAR_GRUNT],
];
const evilTeamAdmins = [
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
[
[TrainerType.TABITHA, TrainerType.COURTNEY],
[TrainerType.MATT, TrainerType.SHELLY],
],
[TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN],
[TrainerType.ZINZOLIN, TrainerType.COLRESS],
[TrainerType.XEROSIC, TrainerType.BRYONY],
[TrainerType.FABA, TrainerType.PLUMERIA],
[TrainerType.OLEANA],
[TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI],
];
const evilTeamBosses = [
[TrainerType.ROCKET_BOSS_GIOVANNI_1],
[TrainerType.ROCKET_BOSS_GIOVANNI_1],
[TrainerType.MAXIE, TrainerType.ARCHIE],
[TrainerType.CYRUS],
[TrainerType.GHETSIS],
[TrainerType.LYSANDRE],
[TrainerType.LUSAMINE, TrainerType.GUZMA],
[TrainerType.ROSE],
[TrainerType.PENNY],
];
const evilTeamBossRematches = [
[TrainerType.ROCKET_BOSS_GIOVANNI_2],
[TrainerType.ROCKET_BOSS_GIOVANNI_2],
[TrainerType.MAXIE_2, TrainerType.ARCHIE_2],
[TrainerType.CYRUS_2],
[TrainerType.GHETSIS_2],
[TrainerType.LYSANDRE_2],
[TrainerType.LUSAMINE_2, TrainerType.GUZMA_2],
[TrainerType.ROSE_2],
[TrainerType.PENNY_2],
];
switch (waveIndex) {
case ClassicFixedBossWaves.EVIL_GRUNT_1:
trainerTypes = evilTeamGrunts[this.value - 1];
@ -488,42 +536,123 @@ export class SingleGenerationChallenge extends Challenge {
break;
case ClassicFixedBossWaves.EVIL_BOSS_1:
trainerTypes = evilTeamBosses[this.value - 1];
battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false });
battleConfig
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
allowLuckUpgrades: false,
});
return true;
case ClassicFixedBossWaves.EVIL_BOSS_2:
trainerTypes = evilTeamBossRematches[this.value - 1];
battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false });
battleConfig
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
allowLuckUpgrades: false,
});
return true;
case ClassicFixedBossWaves.ELITE_FOUR_1:
trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ];
trainerTypes = [
TrainerType.LORELEI,
TrainerType.WILL,
TrainerType.SIDNEY,
TrainerType.AARON,
TrainerType.SHAUNTAL,
TrainerType.MALVA,
Utils.randSeedItem([TrainerType.HALA, TrainerType.MOLAYNE]),
TrainerType.MARNIE_ELITE,
TrainerType.RIKA,
];
break;
case ClassicFixedBossWaves.ELITE_FOUR_2:
trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ];
trainerTypes = [
TrainerType.BRUNO,
TrainerType.KOGA,
TrainerType.PHOEBE,
TrainerType.BERTHA,
TrainerType.MARSHAL,
TrainerType.SIEBOLD,
TrainerType.OLIVIA,
TrainerType.NESSA_ELITE,
TrainerType.POPPY,
];
break;
case ClassicFixedBossWaves.ELITE_FOUR_3:
trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ];
trainerTypes = [
TrainerType.AGATHA,
TrainerType.BRUNO,
TrainerType.GLACIA,
TrainerType.FLINT,
TrainerType.GRIMSLEY,
TrainerType.WIKSTROM,
TrainerType.ACEROLA,
Utils.randSeedItem([TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE]),
TrainerType.LARRY_ELITE,
];
break;
case ClassicFixedBossWaves.ELITE_FOUR_4:
trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ];
trainerTypes = [
TrainerType.LANCE,
TrainerType.KAREN,
TrainerType.DRAKE,
TrainerType.LUCIAN,
TrainerType.CAITLIN,
TrainerType.DRASNA,
TrainerType.KAHILI,
TrainerType.RAIHAN_ELITE,
TrainerType.HASSEL,
];
break;
case ClassicFixedBossWaves.CHAMPION:
trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, Utils.randSeedItem([ TrainerType.KUKUI, TrainerType.HAU ]), Utils.randSeedItem([ TrainerType.LEON, TrainerType.MUSTARD ]), Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ];
trainerTypes = [
TrainerType.BLUE,
Utils.randSeedItem([TrainerType.RED, TrainerType.LANCE_CHAMPION]),
Utils.randSeedItem([TrainerType.STEVEN, TrainerType.WALLACE]),
TrainerType.CYNTHIA,
Utils.randSeedItem([TrainerType.ALDER, TrainerType.IRIS]),
TrainerType.DIANTHA,
Utils.randSeedItem([TrainerType.KUKUI, TrainerType.HAU]),
Utils.randSeedItem([TrainerType.LEON, TrainerType.MUSTARD]),
Utils.randSeedItem([TrainerType.GEETA, TrainerType.NEMONA]),
];
break;
}
if (trainerTypes.length === 0) {
return false;
} else if (evilTeamWaves.includes(waveIndex)) {
battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true));
return true;
} else if (waveIndex >= ClassicFixedBossWaves.ELITE_FOUR_1 && waveIndex <= ClassicFixedBossWaves.CHAMPION) {
const ttypes = trainerTypes as TrainerType[];
battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(() => new Trainer(ttypes[this.value - 1], TrainerVariant.DEFAULT));
return true;
} else {
return false;
}
if (evilTeamWaves.includes(waveIndex)) {
battleConfig
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true));
return true;
}
if (waveIndex >= ClassicFixedBossWaves.ELITE_FOUR_1 && waveIndex <= ClassicFixedBossWaves.CHAMPION) {
const ttypes = trainerTypes as TrainerType[];
battleConfig
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(() => new Trainer(ttypes[this.value - 1], TrainerVariant.DEFAULT));
return true;
}
return false;
}
/**
@ -556,10 +685,11 @@ export class SingleGenerationChallenge extends Challenge {
if (value === 0) {
return i18next.t("challenges:singleGeneration.desc_default");
}
return i18next.t("challenges:singleGeneration.desc", { gen: i18next.t(`challenges:singleGeneration.gen_${value}`) });
return i18next.t("challenges:singleGeneration.desc", {
gen: i18next.t(`challenges:singleGeneration.gen_${value}`),
});
}
static loadChallenge(source: SingleGenerationChallenge | any): SingleGenerationChallenge {
const newChallenge = new SingleGenerationChallenge();
newChallenge.value = source.value;
@ -572,7 +702,7 @@ interface monotypeOverride {
/** The species to override */
species: Species;
/** The type to count as */
type: Type;
type: PokemonType;
/** If part of a fusion, should we check the fused species instead of the base species? */
fusion: boolean;
}
@ -582,39 +712,17 @@ interface monotypeOverride {
*/
export class SingleTypeChallenge extends Challenge {
private static TYPE_OVERRIDES: monotypeOverride[] = [
{ species: Species.CASTFORM, type: Type.NORMAL, fusion: false },
{ species: Species.CASTFORM, type: PokemonType.NORMAL, fusion: false },
];
// TODO: Find a solution for all Pokemon with this ssui issue, including Basculin and Burmy
private static SPECIES_OVERRIDES: Species[] = [ Species.MELOETTA ];
constructor() {
super(Challenges.SINGLE_TYPE, 18);
}
override applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
override applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps): boolean {
const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex);
const types = [speciesForm.type1, speciesForm.type2];
if (soft && !SingleTypeChallenge.SPECIES_OVERRIDES.includes(pokemon.speciesId)) {
const speciesToCheck = [ pokemon.speciesId ];
while (speciesToCheck.length) {
const checking = speciesToCheck.pop();
if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId);
types.push(getPokemonSpecies(e.speciesId).type1, getPokemonSpecies(e.speciesId).type2);
});
}
if (checking && pokemonFormChanges.hasOwnProperty(checking)) {
pokemonFormChanges[checking].forEach(f1 => {
getPokemonSpecies(checking).forms.forEach(f2 => {
if (f1.formKey === f2.formKey) {
types.push(f2.type1, f2.type2);
}
});
});
}
}
}
if (!types.includes(this.value - 1)) {
valid.value = false;
return true;
@ -623,8 +731,16 @@ export class SingleTypeChallenge extends Challenge {
}
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
if (pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true)
&& !SingleTypeChallenge.TYPE_OVERRIDES.some(o => o.type === (this.value - 1) && (pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies! : pokemon.species).speciesId === o.species)) { // TODO: is the bang on fusionSpecies correct?
if (
pokemon.isPlayer() &&
!pokemon.isOfType(this.value - 1, false, false, true) &&
!SingleTypeChallenge.TYPE_OVERRIDES.some(
o =>
o.type === this.value - 1 &&
(pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies! : pokemon.species).speciesId === o.species,
)
) {
// TODO: is the bang on fusionSpecies correct?
valid.value = false;
return true;
}
@ -647,7 +763,7 @@ export class SingleTypeChallenge extends Challenge {
if (overrideValue === undefined) {
overrideValue = this.value;
}
return Type[this.value - 1].toLowerCase();
return PokemonType[this.value - 1].toLowerCase();
}
/**
@ -659,10 +775,12 @@ export class SingleTypeChallenge extends Challenge {
if (overrideValue === undefined) {
overrideValue = this.value;
}
const type = i18next.t(`pokemonInfo:Type.${Type[this.value - 1]}`);
const typeColor = `[color=${TypeColor[Type[this.value - 1]]}][shadow=${TypeShadow[Type[this.value - 1]]}]${type}[/shadow][/color]`;
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]`;
const defaultDesc = i18next.t("challenges:singleType.desc_default");
const typeDesc = i18next.t("challenges:singleType.desc", { type: typeColor });
const typeDesc = i18next.t("challenges:singleType.desc", {
type: typeColor,
});
return this.value === 0 ? defaultDesc : typeDesc;
}
@ -702,7 +820,12 @@ export class FreshStartChallenge extends Challenge {
pokemon.abilityIndex = 0; // Always base ability, not hidden ability
pokemon.passive = false; // Passive isn't unlocked
pokemon.nature = Nature.HARDY; // Neutral nature
pokemon.moveset = pokemon.species.getLevelMoves().filter(m => m[0] <= 5).map(lm => lm[1]).slice(0, 4).map(m => new PokemonMove(m)); // No egg moves
pokemon.moveset = pokemon.species
.getLevelMoves()
.filter(m => m[0] <= 5)
.map(lm => lm[1])
.slice(0, 4)
.map(m => new PokemonMove(m)); // No egg moves
pokemon.luck = 0; // No luck
pokemon.shiny = false; // Not shiny
pokemon.variant = 0; // Not shiny
@ -747,7 +870,8 @@ export class InverseBattleChallenge extends Challenge {
if (effectiveness.value < 1) {
effectiveness.value = 2;
return true;
} else if (effectiveness.value > 1) {
}
if (effectiveness.value > 1) {
effectiveness.value = 0.5;
return true;
}
@ -764,7 +888,7 @@ export class FlipStatChallenge extends Challenge {
super(Challenges.FLIP_STAT, 1);
}
override applyFlipStat(pokemon: Pokemon, baseStats: number[]) {
override applyFlipStat(_pokemon: Pokemon, baseStats: number[]) {
const origStats = Utils.deepCopy(baseStats);
baseStats[0] = origStats[5];
baseStats[1] = origStats[4];
@ -855,10 +979,15 @@ export class LowerStarterPointsChallenge extends Challenge {
* @param pokemon {@link PokemonSpecies} The pokemon to check the validity of.
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon.
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_CHOICE, pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.STARTER_CHOICE,
pokemon: PokemonSpecies,
valid: Utils.BooleanHolder,
dexAttr: DexAttrProps,
): boolean;
/**
* Apply all challenges that modify available total starter points.
* @param gameMode {@link GameMode} The current gameMode
@ -866,7 +995,11 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param points {@link Utils.NumberHolder} The amount of points you have available.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_POINTS, points: Utils.NumberHolder): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.STARTER_POINTS,
points: Utils.NumberHolder,
): boolean;
/**
* Apply all challenges that modify the cost of a starter.
* @param gameMode {@link GameMode} The current gameMode
@ -875,7 +1008,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param points {@link Utils.NumberHolder} The cost of the pokemon.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_COST, species: Species, cost: Utils.NumberHolder): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.STARTER_COST,
species: Species,
cost: Utils.NumberHolder,
): boolean;
/**
* Apply all challenges that modify a starter after selection.
* @param gameMode {@link GameMode} The current gameMode
@ -883,7 +1021,11 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param pokemon {@link Pokemon} The starter pokemon to modify.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_MODIFY, pokemon: Pokemon): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.STARTER_MODIFY,
pokemon: Pokemon,
): boolean;
/**
* Apply all challenges that what pokemon you can have in battle.
* @param gameMode {@link GameMode} The current gameMode
@ -892,7 +1034,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.POKEMON_IN_BATTLE, pokemon: Pokemon, valid: Utils.BooleanHolder): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.POKEMON_IN_BATTLE,
pokemon: Pokemon,
valid: Utils.BooleanHolder,
): boolean;
/**
* Apply all challenges that modify what fixed battles there are.
* @param gameMode {@link GameMode} The current gameMode
@ -901,7 +1048,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param battleConfig {@link FixedBattleConfig} The battle config to modify.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FIXED_BATTLES, waveIndex: Number, battleConfig: FixedBattleConfig): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.FIXED_BATTLES,
waveIndex: number,
battleConfig: FixedBattleConfig,
): boolean;
/**
* Apply all challenges that modify type effectiveness.
* @param gameMode {@linkcode GameMode} The current gameMode
@ -909,7 +1061,11 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.TYPE_EFFECTIVENESS, effectiveness: Utils.NumberHolder): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.TYPE_EFFECTIVENESS,
effectiveness: Utils.NumberHolder,
): boolean;
/**
* Apply all challenges that modify what level AI are.
* @param gameMode {@link GameMode} The current gameMode
@ -920,7 +1076,14 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.AI_LEVEL, level: Utils.NumberHolder, levelCap: number, isTrainer: boolean, isBoss: boolean): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.AI_LEVEL,
level: Utils.NumberHolder,
levelCap: number,
isTrainer: boolean,
isBoss: boolean,
): boolean;
/**
* Apply all challenges that modify how many move slots the AI has.
* @param gameMode {@link GameMode} The current gameMode
@ -929,7 +1092,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param moveSlots {@link Utils.NumberHolder} The amount of move slots.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.AI_MOVE_SLOTS, pokemon: Pokemon, moveSlots: Utils.NumberHolder): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.AI_MOVE_SLOTS,
pokemon: Pokemon,
moveSlots: Utils.NumberHolder,
): boolean;
/**
* Apply all challenges that modify whether a pokemon has its passive.
* @param gameMode {@link GameMode} The current gameMode
@ -938,7 +1106,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param hasPassive {@link Utils.BooleanHolder} Whether it has its passive.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.PASSIVE_ACCESS, pokemon: Pokemon, hasPassive: Utils.BooleanHolder): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.PASSIVE_ACCESS,
pokemon: Pokemon,
hasPassive: Utils.BooleanHolder,
): boolean;
/**
* Apply all challenges that modify the game modes settings.
* @param gameMode {@link GameMode} The current gameMode
@ -956,7 +1129,14 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param level {@link Utils.NumberHolder} The level threshold for access.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_ACCESS, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.NumberHolder): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.MOVE_ACCESS,
pokemon: Pokemon,
moveSource: MoveSourceType,
move: Moves,
level: Utils.NumberHolder,
): boolean;
/**
* Apply all challenges that modify what weight a pokemon gives to move generation
* @param gameMode {@link GameMode} The current gameMode
@ -967,9 +1147,21 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param weight {@link Utils.NumberHolder} The weight of the move.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_WEIGHT, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, weight: Utils.NumberHolder): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.MOVE_WEIGHT,
pokemon: Pokemon,
moveSource: MoveSourceType,
move: Moves,
weight: Utils.NumberHolder,
): boolean;
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FLIP_STAT, pokemon: Pokemon, baseStats: number[]): boolean;
export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.FLIP_STAT,
pokemon: Pokemon,
baseStats: number[],
): boolean;
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean {
let ret = false;
@ -977,7 +1169,7 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
if (c.value !== 0) {
switch (challengeType) {
case ChallengeType.STARTER_CHOICE:
ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]);
ret ||= c.applyStarterChoice(args[0], args[1], args[2]);
break;
case ChallengeType.STARTER_POINTS:
ret ||= c.applyStarterPoints(args[0]);
@ -1057,6 +1249,90 @@ export function initChallenges() {
new SingleTypeChallenge(),
new FreshStartChallenge(),
new InverseBattleChallenge(),
new FlipStatChallenge()
new FlipStatChallenge(),
);
}
/**
* Apply all challenges to the given starter (and form) to check its validity.
* Differs from {@linkcode checkSpeciesValidForChallenge} which only checks form changes.
* @param species - The {@linkcode PokemonSpecies} to check the validity of.
* @param dexAttr - The {@linkcode DexAttrProps | dex attributes} of the species, including its form index.
* @param soft - If `true`, allow it if it could become valid through evolution or form change.
* @returns `true` if the species is considered valid.
*/
export function checkStarterValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) {
if (!soft) {
const isValidForChallenge = new Utils.BooleanHolder(true);
applyChallenges(globalScene.gameMode, ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props);
return isValidForChallenge.value;
}
// We check the validity of every evolution and form change, and require that at least one is valid
const speciesToCheck = [species.speciesId];
while (speciesToCheck.length) {
const checking = speciesToCheck.pop();
// Linter complains if we don't handle this
if (!checking) {
return false;
}
const checkingSpecies = getPokemonSpecies(checking);
if (checkSpeciesValidForChallenge(checkingSpecies, props, true)) {
return true;
}
if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
pokemonEvolutions[checking].forEach(e => {
// Form check to deal with cases such as Basculin -> Basculegion
// TODO: does this miss anything if checking forms of a stage 2 Pokémon?
if (!e?.preFormKey || e.preFormKey === species.forms[props.formIndex].formKey) {
speciesToCheck.push(e.speciesId);
}
});
}
}
return false;
}
/**
* Apply all challenges to the given species (and form) to check its validity.
* Differs from {@linkcode checkStarterValidForChallenge} which also checks evolutions.
* @param species - The {@linkcode PokemonSpecies} to check the validity of.
* @param dexAttr - The {@linkcode DexAttrProps | dex attributes} of the species, including its form index.
* @param soft - If `true`, allow it if it could become valid through a form change.
* @returns `true` if the species is considered valid.
*/
function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) {
const isValidForChallenge = new Utils.BooleanHolder(true);
applyChallenges(globalScene.gameMode, ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props);
if (!soft || !pokemonFormChanges.hasOwnProperty(species.speciesId)) {
return isValidForChallenge.value;
}
// If the form in props is valid, return true before checking other form changes
if (soft && isValidForChallenge.value) {
return true;
}
pokemonFormChanges[species.speciesId].forEach(f1 => {
// Exclude form changes that require the mon to be on the field to begin with,
// such as Castform
if (!("item" in f1)) {
return;
}
species.forms.forEach((f2, formIndex) => {
if (f1.formKey === f2.formKey) {
const formProps = { ...props };
formProps.formIndex = formIndex;
const isFormValidForChallenge = new Utils.BooleanHolder(true);
applyChallenges(
globalScene.gameMode,
ChallengeType.STARTER_CHOICE,
species,
isFormValidForChallenge,
formProps,
);
if (isFormValidForChallenge.value) {
return true;
}
}
});
});
return false;
}

View File

@ -1,5 +1,5 @@
import type { Abilities } from "#enums/abilities";
import type { Type } from "#enums/type";
import type { PokemonType } from "#enums/pokemon-type";
import { isNullOrUndefined } from "#app/utils";
import type { Nature } from "#enums/nature";
@ -13,7 +13,7 @@ export class CustomPokemonData {
public ability: Abilities | -1;
public passive: Abilities | -1;
public nature: Nature | -1;
public types: Type[];
public types: PokemonType[];
/** `hitsReceivedCount` aka `hitsRecCount` saves how often the pokemon got hit until a new arena encounter (used for Rage Fist) */
public hitsRecCount: number;

View File

@ -16,7 +16,7 @@ export interface DailyRunConfig {
}
export function fetchDailyRunSeed(): Promise<string | null> {
return new Promise<string | null>((resolve, reject) => {
return new Promise<string | null>((resolve, _reject) => {
pokerogueApi.daily.getSeed().then(dailySeed => {
resolve(dailySeed);
});
@ -26,13 +26,17 @@ export function fetchDailyRunSeed(): Promise<string | null> {
export function getDailyRunStarters(seed: string): Starter[] {
const starters: Starter[] = [];
globalScene.executeWithSeedOffset(() => {
globalScene.executeWithSeedOffset(
() => {
const startingLevel = globalScene.gameMode.getStartingLevel();
if (/\d{18}$/.test(seed)) {
for (let s = 0; s < 3; s++) {
const offset = 6 + s * 6;
const starterSpeciesForm = getPokemonSpeciesForm(parseInt(seed.slice(offset, offset + 4)) as Species, parseInt(seed.slice(offset + 4, offset + 6)));
const starterSpeciesForm = getPokemonSpeciesForm(
Number.parseInt(seed.slice(offset, offset + 4)) as Species,
Number.parseInt(seed.slice(offset + 4, offset + 6)),
);
starters.push(getDailyRunStarter(starterSpeciesForm, startingLevel));
}
return;
@ -46,35 +50,52 @@ export function getDailyRunStarters(seed: string): Starter[] {
for (let c = 0; c < starterCosts.length; c++) {
const cost = starterCosts[c];
const costSpecies = Object.keys(speciesStarterCosts)
.map(s => parseInt(s) as Species)
.map(s => Number.parseInt(s) as Species)
.filter(s => speciesStarterCosts[s] === cost);
const randPkmSpecies = getPokemonSpecies(Utils.randSeedItem(costSpecies));
const starterSpecies = getPokemonSpecies(randPkmSpecies.getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER));
const starterSpecies = getPokemonSpecies(
randPkmSpecies.getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER),
);
starters.push(getDailyRunStarter(starterSpecies, startingLevel));
}
}, 0, seed);
},
0,
seed,
);
return starters;
}
function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLevel: number): Starter {
const starterSpecies = starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId);
const starterSpecies =
starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId);
const formIndex = starterSpeciesForm instanceof PokemonSpecies ? undefined : starterSpeciesForm.formIndex;
const pokemon = new PlayerPokemon(starterSpecies, startingLevel, undefined, formIndex, undefined, undefined, undefined, undefined, undefined, undefined);
const pokemon = new PlayerPokemon(
starterSpecies,
startingLevel,
undefined,
formIndex,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
);
const starter: Starter = {
species: starterSpecies,
dexAttr: pokemon.getDexAttr(),
abilityIndex: pokemon.abilityIndex,
passive: false,
nature: pokemon.getNature(),
pokerus: pokemon.pokerus
pokerus: pokemon.pokerus,
};
pokemon.destroy();
return starter;
}
interface BiomeWeights {
[key: number]: number
[key: number]: number;
}
// Initially weighted by amount of exits each biome has

File diff suppressed because it is too large Load Diff

View File

@ -45,7 +45,7 @@ export class EggHatchData {
seenCount: currDexEntry.seenCount,
caughtCount: currDexEntry.caughtCount,
hatchedCount: currDexEntry.hatchedCount,
ivs: [ ...currDexEntry.ivs ]
ivs: [...currDexEntry.ivs],
};
this.starterDataEntryBeforeUpdate = {
moveset: currStarterDataEntry.moveset,
@ -55,7 +55,7 @@ export class EggHatchData {
abilityAttr: currStarterDataEntry.abilityAttr,
passiveAttr: currStarterDataEntry.passiveAttr,
valueReduction: currStarterDataEntry.valueReduction,
classicWinCount: currStarterDataEntry.classicWinCount
classicWinCount: currStarterDataEntry.classicWinCount,
};
}
@ -81,11 +81,11 @@ export class EggHatchData {
* @param showMessage boolean to show messages for the new catches and egg moves (false by default)
* @returns
*/
updatePokemon(showMessage : boolean = false) {
updatePokemon(showMessage = false) {
return new Promise<void>(resolve => {
globalScene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => {
globalScene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
globalScene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then((value) => {
globalScene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then(value => {
this.setEggMoveUnlocked(value);
resolve();
});

View File

@ -12,7 +12,31 @@ import i18next from "i18next";
import { EggTier } from "#enums/egg-type";
import { Species } from "#enums/species";
import { EggSourceType } from "#enums/egg-source-types";
import { MANAPHY_EGG_MANAPHY_RATE, SAME_SPECIES_EGG_HA_RATE, GACHA_EGG_HA_RATE, GACHA_DEFAULT_RARE_EGGMOVE_RATE, SAME_SPECIES_EGG_RARE_EGGMOVE_RATE, GACHA_MOVE_UP_RARE_EGGMOVE_RATE, GACHA_DEFAULT_SHINY_RATE, GACHA_SHINY_UP_SHINY_RATE, SAME_SPECIES_EGG_SHINY_RATE, EGG_PITY_LEGENDARY_THRESHOLD, EGG_PITY_EPIC_THRESHOLD, EGG_PITY_RARE_THRESHOLD, SHINY_VARIANT_CHANCE, SHINY_EPIC_CHANCE, GACHA_DEFAULT_COMMON_EGG_THRESHOLD, GACHA_DEFAULT_RARE_EGG_THRESHOLD, GACHA_DEFAULT_EPIC_EGG_THRESHOLD, GACHA_LEGENDARY_UP_THRESHOLD_OFFSET, HATCH_WAVES_MANAPHY_EGG, HATCH_WAVES_COMMON_EGG, HATCH_WAVES_RARE_EGG, HATCH_WAVES_EPIC_EGG, HATCH_WAVES_LEGENDARY_EGG } from "#app/data/balance/rates";
import {
MANAPHY_EGG_MANAPHY_RATE,
SAME_SPECIES_EGG_HA_RATE,
GACHA_EGG_HA_RATE,
GACHA_DEFAULT_RARE_EGGMOVE_RATE,
SAME_SPECIES_EGG_RARE_EGGMOVE_RATE,
GACHA_MOVE_UP_RARE_EGGMOVE_RATE,
GACHA_DEFAULT_SHINY_RATE,
GACHA_SHINY_UP_SHINY_RATE,
SAME_SPECIES_EGG_SHINY_RATE,
EGG_PITY_LEGENDARY_THRESHOLD,
EGG_PITY_EPIC_THRESHOLD,
EGG_PITY_RARE_THRESHOLD,
SHINY_VARIANT_CHANCE,
SHINY_EPIC_CHANCE,
GACHA_DEFAULT_COMMON_EGG_THRESHOLD,
GACHA_DEFAULT_RARE_EGG_THRESHOLD,
GACHA_DEFAULT_EPIC_EGG_THRESHOLD,
GACHA_LEGENDARY_UP_THRESHOLD_OFFSET,
HATCH_WAVES_MANAPHY_EGG,
HATCH_WAVES_COMMON_EGG,
HATCH_WAVES_RARE_EGG,
HATCH_WAVES_EPIC_EGG,
HATCH_WAVES_LEGENDARY_EGG,
} from "#app/data/balance/rates";
import { speciesEggTiers } from "#app/data/balance/species-egg-tiers";
export const EGG_SEED = 1073741824;
@ -60,7 +84,6 @@ export interface IEggOptions {
}
export class Egg {
////
// #region Private properties
////
@ -141,7 +164,7 @@ export class Egg {
this._sourceType = eggOptions?.sourceType!; // TODO: is this bang correct?
// Ensure _sourceType is defined before invoking rollEggTier(), as it is referenced
this._tier = eggOptions?.tier ?? (Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier());
this._tier = eggOptions?.tier ?? Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier();
// If egg was pulled, check if egg pity needs to override the egg tier
if (eggOptions?.pulled) {
// Needs this._tier and this._sourceType to work
@ -156,7 +179,7 @@ export class Egg {
// First roll shiny and variant so we can filter if species with an variant exist
this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
this._variantTier = eggOptions?.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant());
this._variantTier = eggOptions?.variantTier ?? Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant();
this._species = eggOptions?.species ?? this.rollSpecies()!; // TODO: Is this bang correct?
this._overrideHiddenAbility = eggOptions?.overrideHiddenAbility ?? false;
@ -181,9 +204,13 @@ export class Egg {
};
const seedOverride = Utils.randomString(24);
globalScene.executeWithSeedOffset(() => {
globalScene.executeWithSeedOffset(
() => {
generateEggProperties(eggOptions);
}, 0, seedOverride);
},
0,
seedOverride,
);
this._eggDescriptor = eggOptions?.eggDescriptor;
}
@ -193,8 +220,11 @@ export class Egg {
////
public isManaphyEgg(): boolean {
return (this._species === Species.PHIONE || this._species === Species.MANAPHY) ||
this._tier === EggTier.COMMON && !(this._id % 204) && !this._species;
return (
this._species === Species.PHIONE ||
this._species === Species.MANAPHY ||
(this._tier === EggTier.COMMON && !(this._id % 204) && !this._species)
);
}
public getKey(): string {
@ -218,14 +248,18 @@ export class Egg {
let pokemonSpecies = getPokemonSpecies(this._species);
// Special condition to have Phione eggs also have a chance of generating Manaphy
if (this._species === Species.PHIONE && this._sourceType === EggSourceType.SAME_SPECIES_EGG) {
pokemonSpecies = getPokemonSpecies(Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY);
pokemonSpecies = getPokemonSpecies(
Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY,
);
}
// Sets the hidden ability if a hidden ability exists and
// the override is set or the egg hits the chance
let abilityIndex: number | undefined = undefined;
const sameSpeciesEggHACheck = (this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE));
const gachaEggHACheck = (!(this._sourceType === EggSourceType.SAME_SPECIES_EGG) && !Utils.randSeedInt(GACHA_EGG_HA_RATE));
const sameSpeciesEggHACheck =
this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE);
const gachaEggHACheck =
!(this._sourceType === EggSourceType.SAME_SPECIES_EGG) && !Utils.randSeedInt(GACHA_EGG_HA_RATE);
if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility || sameSpeciesEggHACheck || gachaEggHACheck)) {
abilityIndex = 2;
}
@ -243,9 +277,13 @@ export class Egg {
};
ret = ret!; // Tell TS compiler it's defined now
globalScene.executeWithSeedOffset(() => {
globalScene.executeWithSeedOffset(
() => {
generatePlayerPokemonHelper();
}, this._id, EGG_SEED.toString());
},
this._id,
EGG_SEED.toString(),
);
return ret;
}
@ -287,9 +325,17 @@ export class Egg {
public getEggTypeDescriptor(): string {
switch (this.sourceType) {
case EggSourceType.SAME_SPECIES_EGG:
return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() });
return (
this._eggDescriptor ??
i18next.t("egg:sameSpeciesEgg", {
species: getPokemonSpecies(this._species).getName(),
})
);
case EggSourceType.GACHA_LEGENDARY:
return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.timestamp)).getName()})`;
return (
this._eggDescriptor ??
`${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.timestamp)).getName()})`
);
case EggSourceType.GACHA_SHINY:
return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny");
case EggSourceType.GACHA_MOVE:
@ -344,9 +390,16 @@ export class Egg {
}
private rollEggTier(): EggTier {
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
const tierValueOffset =
this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
const tierValue = Utils.randInt(256);
return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.RARE : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.EPIC : EggTier.LEGENDARY;
return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset
? EggTier.COMMON
: tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset
? EggTier.RARE
: tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset
? EggTier.EPIC
: EggTier.LEGENDARY;
}
private rollSpecies(): Species | null {
@ -364,10 +417,10 @@ export class Egg {
* when Utils.randSeedInt(8) = 1, and by making the generatePlayerPokemon() species
* check pass when Utils.randSeedInt(8) = 0, we can tell them apart during tests.
*/
const rand = (Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1);
const rand = Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1;
return rand ? Species.PHIONE : Species.MANAPHY;
} else if (this.tier === EggTier.LEGENDARY
&& this._sourceType === EggSourceType.GACHA_LEGENDARY) {
}
if (this.tier === EggTier.LEGENDARY && this._sourceType === EggSourceType.GACHA_LEGENDARY) {
if (!Utils.randSeedInt(2)) {
return getLegendaryGachaSpeciesForTimestamp(this.timestamp);
}
@ -399,13 +452,21 @@ export class Egg {
let speciesPool = Object.keys(speciesEggTiers)
.filter(s => speciesEggTiers[s] === this.tier)
.map(s => parseInt(s) as Species)
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1);
.map(s => Number.parseInt(s) as Species)
.filter(
s =>
!pokemonPrevolutions.hasOwnProperty(s) &&
getPokemonSpecies(s).isObtainable() &&
ignoredSpecies.indexOf(s) === -1,
);
// If this is the 10th egg without unlocking something new, attempt to force it.
if (globalScene.gameData.unlockPity[this.tier] >= 9) {
const lockedPool = speciesPool.filter(s => !globalScene.gameData.dexData[s].caughtAttr && !globalScene.gameData.eggs.some(e => e.species === s));
if (lockedPool.length) { // Skip this if everything is unlocked
const lockedPool = speciesPool.filter(
s => !globalScene.gameData.dexData[s].caughtAttr && !globalScene.gameData.eggs.some(e => e.species === s),
);
if (lockedPool.length) {
// Skip this if everything is unlocked
speciesPool = lockedPool;
}
}
@ -431,7 +492,9 @@ export class Egg {
for (const speciesId of speciesPool) {
// Accounts for species that have starter costs outside of the normal range for their EggTier
const speciesCostClamped = Phaser.Math.Clamp(speciesStarterCosts[speciesId], minStarterValue, maxStarterValue);
const weight = Math.floor((((maxStarterValue - speciesCostClamped) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
const weight = Math.floor(
(((maxStarterValue - speciesCostClamped) / (maxStarterValue - minStarterValue + 1)) * 1.5 + 1) * 100,
);
speciesWeights.push(totalWeight + weight);
totalWeight += weight;
}
@ -447,7 +510,10 @@ export class Egg {
}
species = species!; // tell TS compiled it's defined now!
if (globalScene.gameData.dexData[species].caughtAttr || globalScene.gameData.eggs.some(e => e.species === species)) {
if (
globalScene.gameData.dexData[species].caughtAttr ||
globalScene.gameData.eggs.some(e => e.species === species)
) {
globalScene.gameData.unlockPity[this.tier] = Math.min(globalScene.gameData.unlockPity[this.tier] + 1, 10);
} else {
globalScene.gameData.unlockPity[this.tier] = 0;
@ -487,20 +553,24 @@ export class Egg {
const rand = Utils.randSeedInt(10);
if (rand >= SHINY_VARIANT_CHANCE) {
return VariantTier.STANDARD; // 6/10
} else if (rand >= SHINY_EPIC_CHANCE) {
return VariantTier.RARE; // 3/10
} else {
return VariantTier.EPIC; // 1/10
}
if (rand >= SHINY_EPIC_CHANCE) {
return VariantTier.RARE; // 3/10
}
return VariantTier.EPIC; // 1/10
}
private checkForPityTierOverrides(): void {
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
const tierValueOffset =
this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
globalScene.gameData.eggPity[EggTier.RARE] += 1;
globalScene.gameData.eggPity[EggTier.EPIC] += 1;
globalScene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset;
// These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered.
if (globalScene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) {
if (
globalScene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD &&
this._tier === EggTier.COMMON
) {
this._tier = EggTier.LEGENDARY;
} else if (globalScene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.EPIC;
@ -542,7 +612,7 @@ export class Egg {
export function getValidLegendaryGachaSpecies(): Species[] {
return Object.entries(speciesEggTiers)
.filter(s => s[1] === EggTier.LEGENDARY)
.map(s => parseInt(s[0]))
.map(s => Number.parseInt(s[0]))
.filter(s => getPokemonSpecies(s).isObtainable() && s !== Species.ETERNATUS);
}
@ -557,9 +627,13 @@ export function getLegendaryGachaSpeciesForTimestamp(timestamp: number): Species
const offset = Math.floor(Math.floor(dayTimestamp / 86400000) / legendarySpecies.length); // Cycle number
const index = Math.floor(dayTimestamp / 86400000) % legendarySpecies.length; // Index within cycle
globalScene.executeWithSeedOffset(() => {
globalScene.executeWithSeedOffset(
() => {
ret = Phaser.Math.RND.shuffle(legendarySpecies)[index];
}, offset, EGG_SEED.toString());
},
offset,
EGG_SEED.toString(),
);
ret = ret!; // tell TS compiler it's
return ret;

View File

@ -4,16 +4,64 @@ export enum GrowthRate {
MEDIUM_FAST,
MEDIUM_SLOW,
SLOW,
FLUCTUATING
FLUCTUATING,
}
const expLevels = [
[ 0, 15, 52, 122, 237, 406, 637, 942, 1326, 1800, 2369, 3041, 3822, 4719, 5737, 6881, 8155, 9564, 11111, 12800, 14632, 16610, 18737, 21012, 23437, 26012, 28737, 31610, 34632, 37800, 41111, 44564, 48155, 51881, 55737, 59719, 63822, 68041, 72369, 76800, 81326, 85942, 90637, 95406, 100237, 105122, 110052, 115015, 120001, 125000, 131324, 137795, 144410, 151165, 158056, 165079, 172229, 179503, 186894, 194400, 202013, 209728, 217540, 225443, 233431, 241496, 249633, 257834, 267406, 276458, 286328, 296358, 305767, 316074, 326531, 336255, 346965, 357812, 367807, 378880, 390077, 400293, 411686, 423190, 433572, 445239, 457001, 467489, 479378, 491346, 501878, 513934, 526049, 536557, 548720, 560922, 571333, 583539, 591882, 600000 ],
[ 0, 6, 21, 51, 100, 172, 274, 409, 583, 800, 1064, 1382, 1757, 2195, 2700, 3276, 3930, 4665, 5487, 6400, 7408, 8518, 9733, 11059, 12500, 14060, 15746, 17561, 19511, 21600, 23832, 26214, 28749, 31443, 34300, 37324, 40522, 43897, 47455, 51200, 55136, 59270, 63605, 68147, 72900, 77868, 83058, 88473, 94119, 100000, 106120, 112486, 119101, 125971, 133100, 140492, 148154, 156089, 164303, 172800, 181584, 190662, 200037, 209715, 219700, 229996, 240610, 251545, 262807, 274400, 286328, 298598, 311213, 324179, 337500, 351180, 365226, 379641, 394431, 409600, 425152, 441094, 457429, 474163, 491300, 508844, 526802, 545177, 563975, 583200, 602856, 622950, 643485, 664467, 685900, 707788, 730138, 752953, 776239, 800000 ],
[ 0, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331, 1728, 2197, 2744, 3375, 4096, 4913, 5832, 6859, 8000, 9261, 10648, 12167, 13824, 15625, 17576, 19683, 21952, 24389, 27000, 29791, 32768, 35937, 39304, 42875, 46656, 50653, 54872, 59319, 64000, 68921, 74088, 79507, 85184, 91125, 97336, 103823, 110592, 117649, 125000, 132651, 140608, 148877, 157464, 166375, 175616, 185193, 195112, 205379, 216000, 226981, 238328, 250047, 262144, 274625, 287496, 300763, 314432, 328509, 343000, 357911, 373248, 389017, 405224, 421875, 438976, 456533, 474552, 493039, 512000, 531441, 551368, 571787, 592704, 614125, 636056, 658503, 681472, 704969, 729000, 753571, 778688, 804357, 830584, 857375, 884736, 912673, 941192, 970299, 1000000 ],
[ 0, 9, 57, 96, 135, 179, 236, 314, 419, 560, 742, 973, 1261, 1612, 2035, 2535, 3120, 3798, 4575, 5460, 6458, 7577, 8825, 10208, 11735, 13411, 15244, 17242, 19411, 21760, 24294, 27021, 29949, 33084, 36435, 40007, 43808, 47846, 52127, 56660, 61450, 66505, 71833, 77440, 83335, 89523, 96012, 102810, 109923, 117360, 125126, 133229, 141677, 150476, 159635, 169159, 179056, 189334, 199999, 211060, 222522, 234393, 246681, 259392, 272535, 286115, 300140, 314618, 329555, 344960, 360838, 377197, 394045, 411388, 429235, 447591, 466464, 485862, 505791, 526260, 547274, 568841, 590969, 613664, 636935, 660787, 685228, 710266, 735907, 762160, 789030, 816525, 844653, 873420, 902835, 932903, 963632, 995030, 1027103, 1059860 ],
[ 0, 10, 33, 80, 156, 270, 428, 640, 911, 1250, 1663, 2160, 2746, 3430, 4218, 5120, 6141, 7290, 8573, 10000, 11576, 13310, 15208, 17280, 19531, 21970, 24603, 27440, 30486, 33750, 37238, 40960, 44921, 49130, 53593, 58320, 63316, 68590, 74148, 80000, 86151, 92610, 99383, 106480, 113906, 121670, 129778, 138240, 147061, 156250, 165813, 175760, 186096, 196830, 207968, 219520, 231491, 243890, 256723, 270000, 283726, 297910, 312558, 327680, 343281, 359370, 375953, 393040, 410636, 428750, 447388, 466560, 486271, 506530, 527343, 548720, 570666, 593190, 616298, 640000, 664301, 689210, 714733, 740880, 767656, 795070, 823128, 851840, 881211, 911250, 941963, 973360, 1005446, 1038230, 1071718, 1105920, 1140841, 1176490, 1212873, 1250000 ],
[ 0, 4, 13, 32, 65, 112, 178, 276, 393, 540, 745, 967, 1230, 1591, 1957, 2457, 3046, 3732, 4526, 5440, 6482, 7666, 9003, 10506, 12187, 14060, 16140, 18439, 20974, 23760, 26811, 30146, 33780, 37731, 42017, 46656, 50653, 55969, 60505, 66560, 71677, 78533, 84277, 91998, 98415, 107069, 114205, 123863, 131766, 142500, 151222, 163105, 172697, 185807, 196322, 210739, 222231, 238036, 250562, 267840, 281456, 300293, 315059, 335544, 351520, 373744, 390991, 415050, 433631, 459620, 479600, 507617, 529063, 559209, 582187, 614566, 639146, 673863, 700115, 737280, 765275, 804997, 834809, 877201, 908905, 954084, 987754, 1035837, 1071552, 1122660, 1160499, 1214753, 1254796, 1312322, 1354652, 1415577, 1460276, 1524731, 1571884, 1640000 ]
[
0, 15, 52, 122, 237, 406, 637, 942, 1326, 1800, 2369, 3041, 3822, 4719, 5737, 6881, 8155, 9564, 11111, 12800, 14632,
16610, 18737, 21012, 23437, 26012, 28737, 31610, 34632, 37800, 41111, 44564, 48155, 51881, 55737, 59719, 63822,
68041, 72369, 76800, 81326, 85942, 90637, 95406, 100237, 105122, 110052, 115015, 120001, 125000, 131324, 137795,
144410, 151165, 158056, 165079, 172229, 179503, 186894, 194400, 202013, 209728, 217540, 225443, 233431, 241496,
249633, 257834, 267406, 276458, 286328, 296358, 305767, 316074, 326531, 336255, 346965, 357812, 367807, 378880,
390077, 400293, 411686, 423190, 433572, 445239, 457001, 467489, 479378, 491346, 501878, 513934, 526049, 536557,
548720, 560922, 571333, 583539, 591882, 600000,
],
[
0, 6, 21, 51, 100, 172, 274, 409, 583, 800, 1064, 1382, 1757, 2195, 2700, 3276, 3930, 4665, 5487, 6400, 7408, 8518,
9733, 11059, 12500, 14060, 15746, 17561, 19511, 21600, 23832, 26214, 28749, 31443, 34300, 37324, 40522, 43897,
47455, 51200, 55136, 59270, 63605, 68147, 72900, 77868, 83058, 88473, 94119, 100000, 106120, 112486, 119101, 125971,
133100, 140492, 148154, 156089, 164303, 172800, 181584, 190662, 200037, 209715, 219700, 229996, 240610, 251545,
262807, 274400, 286328, 298598, 311213, 324179, 337500, 351180, 365226, 379641, 394431, 409600, 425152, 441094,
457429, 474163, 491300, 508844, 526802, 545177, 563975, 583200, 602856, 622950, 643485, 664467, 685900, 707788,
730138, 752953, 776239, 800000,
],
[
0, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331, 1728, 2197, 2744, 3375, 4096, 4913, 5832, 6859, 8000, 9261,
10648, 12167, 13824, 15625, 17576, 19683, 21952, 24389, 27000, 29791, 32768, 35937, 39304, 42875, 46656, 50653,
54872, 59319, 64000, 68921, 74088, 79507, 85184, 91125, 97336, 103823, 110592, 117649, 125000, 132651, 140608,
148877, 157464, 166375, 175616, 185193, 195112, 205379, 216000, 226981, 238328, 250047, 262144, 274625, 287496,
300763, 314432, 328509, 343000, 357911, 373248, 389017, 405224, 421875, 438976, 456533, 474552, 493039, 512000,
531441, 551368, 571787, 592704, 614125, 636056, 658503, 681472, 704969, 729000, 753571, 778688, 804357, 830584,
857375, 884736, 912673, 941192, 970299, 1000000,
],
[
0, 9, 57, 96, 135, 179, 236, 314, 419, 560, 742, 973, 1261, 1612, 2035, 2535, 3120, 3798, 4575, 5460, 6458, 7577,
8825, 10208, 11735, 13411, 15244, 17242, 19411, 21760, 24294, 27021, 29949, 33084, 36435, 40007, 43808, 47846,
52127, 56660, 61450, 66505, 71833, 77440, 83335, 89523, 96012, 102810, 109923, 117360, 125126, 133229, 141677,
150476, 159635, 169159, 179056, 189334, 199999, 211060, 222522, 234393, 246681, 259392, 272535, 286115, 300140,
314618, 329555, 344960, 360838, 377197, 394045, 411388, 429235, 447591, 466464, 485862, 505791, 526260, 547274,
568841, 590969, 613664, 636935, 660787, 685228, 710266, 735907, 762160, 789030, 816525, 844653, 873420, 902835,
932903, 963632, 995030, 1027103, 1059860,
],
[
0, 10, 33, 80, 156, 270, 428, 640, 911, 1250, 1663, 2160, 2746, 3430, 4218, 5120, 6141, 7290, 8573, 10000, 11576,
13310, 15208, 17280, 19531, 21970, 24603, 27440, 30486, 33750, 37238, 40960, 44921, 49130, 53593, 58320, 63316,
68590, 74148, 80000, 86151, 92610, 99383, 106480, 113906, 121670, 129778, 138240, 147061, 156250, 165813, 175760,
186096, 196830, 207968, 219520, 231491, 243890, 256723, 270000, 283726, 297910, 312558, 327680, 343281, 359370,
375953, 393040, 410636, 428750, 447388, 466560, 486271, 506530, 527343, 548720, 570666, 593190, 616298, 640000,
664301, 689210, 714733, 740880, 767656, 795070, 823128, 851840, 881211, 911250, 941963, 973360, 1005446, 1038230,
1071718, 1105920, 1140841, 1176490, 1212873, 1250000,
],
[
0, 4, 13, 32, 65, 112, 178, 276, 393, 540, 745, 967, 1230, 1591, 1957, 2457, 3046, 3732, 4526, 5440, 6482, 7666,
9003, 10506, 12187, 14060, 16140, 18439, 20974, 23760, 26811, 30146, 33780, 37731, 42017, 46656, 50653, 55969,
60505, 66560, 71677, 78533, 84277, 91998, 98415, 107069, 114205, 123863, 131766, 142500, 151222, 163105, 172697,
185807, 196322, 210739, 222231, 238036, 250562, 267840, 281456, 300293, 315059, 335544, 351520, 373744, 390991,
415050, 433631, 459620, 479600, 507617, 529063, 559209, 582187, 614566, 639146, 673863, 700115, 737280, 765275,
804997, 834809, 877201, 908905, 954084, 987754, 1035837, 1071552, 1122660, 1160499, 1214753, 1254796, 1312322,
1354652, 1415577, 1460276, 1524731, 1571884, 1640000,
],
];
export function getLevelTotalExp(level: number, growthRate: GrowthRate): number {
@ -29,22 +77,22 @@ export function getLevelTotalExp(level: number, growthRate: GrowthRate): number
switch (growthRate) {
case GrowthRate.ERRATIC:
ret = (Math.pow(level, 4) + (Math.pow(level, 3) * 2000)) / 3500;
ret = (Math.pow(level, 4) + Math.pow(level, 3) * 2000) / 3500;
break;
case GrowthRate.FAST:
ret = Math.pow(level, 3) * 4 / 5;
ret = (Math.pow(level, 3) * 4) / 5;
break;
case GrowthRate.MEDIUM_FAST:
ret = Math.pow(level, 3);
break;
case GrowthRate.MEDIUM_SLOW:
ret = (Math.pow(level, 3) * 6 / 5) - (15 * Math.pow(level, 2)) + (100 * level) - 140;
ret = (Math.pow(level, 3) * 6) / 5 - 15 * Math.pow(level, 2) + 100 * level - 140;
break;
case GrowthRate.SLOW:
ret = Math.pow(level, 3) * 5 / 4;
ret = (Math.pow(level, 3) * 5) / 4;
break;
case GrowthRate.FLUCTUATING:
ret = (Math.pow(level, 3) * ((level / 2) + 8)) * 4 / (100 + level);
ret = (Math.pow(level, 3) * (level / 2 + 8) * 4) / (100 + level);
break;
}

View File

@ -1,7 +1,7 @@
export enum Gender {
GENDERLESS = -1,
MALE,
FEMALE
FEMALE,
}
export function getGenderSymbol(gender: Gender) {

View File

@ -0,0 +1,242 @@
import { Moves } from "#enums/moves";
/** Set of moves that cannot be called by {@linkcode Moves.METRONOME Metronome} */
export const invalidMetronomeMoves: ReadonlySet<Moves> = new Set([
Moves.AFTER_YOU,
Moves.ASSIST,
Moves.BANEFUL_BUNKER,
Moves.BEAK_BLAST,
Moves.BELCH,
Moves.BESTOW,
Moves.COMEUPPANCE,
Moves.COPYCAT,
Moves.COUNTER,
Moves.CRAFTY_SHIELD,
Moves.DESTINY_BOND,
Moves.DETECT,
Moves.ENDURE,
Moves.FEINT,
Moves.FOCUS_PUNCH,
Moves.FOLLOW_ME,
Moves.HELPING_HAND,
Moves.INSTRUCT,
Moves.KINGS_SHIELD,
Moves.MAT_BLOCK,
Moves.ME_FIRST,
Moves.METRONOME,
Moves.MIMIC,
Moves.MIRROR_COAT,
Moves.MIRROR_MOVE,
Moves.OBSTRUCT,
Moves.PROTECT,
Moves.QUASH,
Moves.QUICK_GUARD,
Moves.RAGE_POWDER,
Moves.REVIVAL_BLESSING,
Moves.SHELL_TRAP,
Moves.SILK_TRAP,
Moves.SKETCH,
Moves.SLEEP_TALK,
Moves.SNATCH,
Moves.SNORE,
Moves.SPIKY_SHIELD,
Moves.SPOTLIGHT,
Moves.STRUGGLE,
Moves.TRANSFORM,
Moves.WIDE_GUARD,
]);
/** Set of moves that cannot be called by {@linkcode Moves.ASSIST Assist} */
export const invalidAssistMoves: ReadonlySet<Moves> = new Set([
Moves.ASSIST,
Moves.BANEFUL_BUNKER,
Moves.BEAK_BLAST,
Moves.BELCH,
Moves.BESTOW,
Moves.BOUNCE,
Moves.CELEBRATE,
Moves.CHATTER,
Moves.CIRCLE_THROW,
Moves.COPYCAT,
Moves.COUNTER,
Moves.DESTINY_BOND,
Moves.DETECT,
Moves.DIG,
Moves.DIVE,
Moves.DRAGON_TAIL,
Moves.ENDURE,
Moves.FEINT,
Moves.FLY,
Moves.FOCUS_PUNCH,
Moves.FOLLOW_ME,
Moves.HELPING_HAND,
Moves.HOLD_HANDS,
Moves.KINGS_SHIELD,
Moves.MAT_BLOCK,
Moves.ME_FIRST,
Moves.METRONOME,
Moves.MIMIC,
Moves.MIRROR_COAT,
Moves.MIRROR_MOVE,
Moves.NATURE_POWER,
Moves.PHANTOM_FORCE,
Moves.PROTECT,
Moves.RAGE_POWDER,
Moves.ROAR,
Moves.SHADOW_FORCE,
Moves.SHELL_TRAP,
Moves.SKETCH,
Moves.SKY_DROP,
Moves.SLEEP_TALK,
Moves.SNATCH,
Moves.SPIKY_SHIELD,
Moves.SPOTLIGHT,
Moves.STRUGGLE,
Moves.SWITCHEROO,
Moves.TRANSFORM,
Moves.TRICK,
Moves.WHIRLWIND,
]);
/** Set of moves that cannot be called by {@linkcode Moves.SLEEP_TALK Sleep Talk} */
export const invalidSleepTalkMoves: ReadonlySet<Moves> = new Set([
Moves.ASSIST,
Moves.BELCH,
Moves.BEAK_BLAST,
Moves.BIDE,
Moves.BOUNCE,
Moves.COPYCAT,
Moves.DIG,
Moves.DIVE,
Moves.FREEZE_SHOCK,
Moves.FLY,
Moves.FOCUS_PUNCH,
Moves.GEOMANCY,
Moves.ICE_BURN,
Moves.ME_FIRST,
Moves.METRONOME,
Moves.MIRROR_MOVE,
Moves.MIMIC,
Moves.PHANTOM_FORCE,
Moves.RAZOR_WIND,
Moves.SHADOW_FORCE,
Moves.SHELL_TRAP,
Moves.SKETCH,
Moves.SKULL_BASH,
Moves.SKY_ATTACK,
Moves.SKY_DROP,
Moves.SLEEP_TALK,
Moves.SOLAR_BLADE,
Moves.SOLAR_BEAM,
Moves.STRUGGLE,
Moves.UPROAR,
]);
/** Set of moves that cannot be copied by {@linkcode Moves.COPYCAT Copycat} */
export const invalidCopycatMoves: ReadonlySet<Moves> = new Set([
Moves.ASSIST,
Moves.BANEFUL_BUNKER,
Moves.BEAK_BLAST,
Moves.BESTOW,
Moves.CELEBRATE,
Moves.CHATTER,
Moves.CIRCLE_THROW,
Moves.COPYCAT,
Moves.COUNTER,
Moves.DESTINY_BOND,
Moves.DETECT,
Moves.DRAGON_TAIL,
Moves.ENDURE,
Moves.FEINT,
Moves.FOCUS_PUNCH,
Moves.FOLLOW_ME,
Moves.HELPING_HAND,
Moves.HOLD_HANDS,
Moves.KINGS_SHIELD,
Moves.MAT_BLOCK,
Moves.ME_FIRST,
Moves.METRONOME,
Moves.MIMIC,
Moves.MIRROR_COAT,
Moves.MIRROR_MOVE,
Moves.PROTECT,
Moves.RAGE_POWDER,
Moves.ROAR,
Moves.SHELL_TRAP,
Moves.SKETCH,
Moves.SLEEP_TALK,
Moves.SNATCH,
Moves.SPIKY_SHIELD,
Moves.SPOTLIGHT,
Moves.STRUGGLE,
Moves.SWITCHEROO,
Moves.TRANSFORM,
Moves.TRICK,
Moves.WHIRLWIND,
]);
export const invalidMirrorMoveMoves: ReadonlySet<Moves> = new Set([
Moves.ACUPRESSURE,
Moves.AFTER_YOU,
Moves.AROMATIC_MIST,
Moves.BEAK_BLAST,
Moves.BELCH,
Moves.CHILLY_RECEPTION,
Moves.COACHING,
Moves.CONVERSION_2,
Moves.COUNTER,
Moves.CRAFTY_SHIELD,
Moves.CURSE,
Moves.DECORATE,
Moves.DOODLE,
Moves.DOOM_DESIRE,
Moves.DRAGON_CHEER,
Moves.ELECTRIC_TERRAIN,
Moves.FINAL_GAMBIT,
Moves.FLORAL_HEALING,
Moves.FLOWER_SHIELD,
Moves.FOCUS_PUNCH,
Moves.FUTURE_SIGHT,
Moves.GEAR_UP,
Moves.GRASSY_TERRAIN,
Moves.GRAVITY,
Moves.GUARD_SPLIT,
Moves.HAIL,
Moves.HAZE,
Moves.HEAL_PULSE,
Moves.HELPING_HAND,
Moves.HOLD_HANDS,
Moves.INSTRUCT,
Moves.ION_DELUGE,
Moves.MAGNETIC_FLUX,
Moves.MAT_BLOCK,
Moves.ME_FIRST,
Moves.MIMIC,
Moves.MIRROR_COAT,
Moves.MIRROR_MOVE,
Moves.MIST,
Moves.MISTY_TERRAIN,
Moves.MUD_SPORT,
Moves.PERISH_SONG,
Moves.POWER_SPLIT,
Moves.PSYCH_UP,
Moves.PSYCHIC_TERRAIN,
Moves.PURIFY,
Moves.QUICK_GUARD,
Moves.RAIN_DANCE,
Moves.REFLECT_TYPE,
Moves.ROLE_PLAY,
Moves.ROTOTILLER,
Moves.SANDSTORM,
Moves.SHELL_TRAP,
Moves.SKETCH,
Moves.SNOWSCAPE,
Moves.SPIT_UP,
Moves.SPOTLIGHT,
Moves.STRUGGLE,
Moves.SUNNY_DAY,
Moves.TEATIME,
Moves.TRANSFORM,
Moves.WATER_SPORT,
Moves.WIDE_GUARD,
]);

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,12 @@
import { globalScene } from "#app/global-scene";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs, } from "#app/data/trainer-config";
import {
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
@ -27,8 +32,9 @@ const namespace = "mysteryEncounters/aTrainersTest";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3816 | GitHub Issue #3816}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ATrainersTestEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST)
export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.A_TRAINERS_TEST,
)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withIntroSpriteConfigs([]) // These are set in onInit()
@ -43,15 +49,9 @@ export const ATrainersTestEncounter: MysteryEncounter =
// Randomly pick from 1 of the 5 stat trainers to spawn
let trainerType: TrainerType;
let spriteKeys;
let spriteKeys: { spriteKey: any; fileRoot: any };
let trainerNameKey: string;
switch (randSeedInt(5)) {
default:
case 0:
trainerType = TrainerType.BUCK;
spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL);
trainerNameKey = "buck";
break;
case 1:
trainerType = TrainerType.CHERYL;
spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY);
@ -72,38 +72,47 @@ export const ATrainersTestEncounter: MysteryEncounter =
spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1);
trainerNameKey = "riley";
break;
default:
trainerType = TrainerType.BUCK;
spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL);
trainerNameKey = "buck";
break;
}
// Dialogue and tokens for trainer
encounter.dialogue.intro = [
{
speaker: `trainerNames:${trainerNameKey}`,
text: `${namespace}:${trainerNameKey}.intro_dialogue`
}
text: `${namespace}:${trainerNameKey}.intro_dialogue`,
},
];
encounter.options[0].dialogue!.selected = [
{
speaker: `trainerNames:${trainerNameKey}`,
text: `${namespace}:${trainerNameKey}.accept`
}
text: `${namespace}:${trainerNameKey}.accept`,
},
];
encounter.options[1].dialogue!.selected = [
{
speaker: `trainerNames:${trainerNameKey}`,
text: `${namespace}:${trainerNameKey}.decline`
}
text: `${namespace}:${trainerNameKey}.decline`,
},
];
encounter.setDialogueToken("statTrainerName", i18next.t(`trainerNames:${trainerNameKey}`));
const eggDescription = i18next.t(`${namespace}:title`) + ":\n" + i18next.t(`trainerNames:${trainerNameKey}`);
encounter.misc = { trainerType, trainerNameKey, trainerEggDescription: eggDescription };
const eggDescription = `${i18next.t(`${namespace}:title`)}:\n${i18next.t(`trainerNames:${trainerNameKey}`)}`;
encounter.misc = {
trainerType,
trainerNameKey,
trainerEggDescription: eggDescription,
};
// Trainer config
const trainerConfig = trainerConfigs[trainerType].clone();
const trainerSpriteKey = trainerConfig.getSpriteKey();
encounter.enemyPartyConfigs.push({
levelAdditiveModifier: 1,
trainerConfig: trainerConfig
trainerConfig: trainerConfig,
});
encounter.spriteConfigs = [
@ -115,7 +124,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
isPokemon: true,
x: 22,
y: -2,
yShadow: -2
yShadow: -2,
},
{
spriteKey: trainerSpriteKey,
@ -124,8 +133,8 @@ export const ATrainersTestEncounter: MysteryEncounter =
disableAnimation: true,
x: -24,
y: 4,
yShadow: 4
}
yShadow: 4,
},
];
return true;
@ -138,7 +147,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
.withSimpleOption(
{
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`
buttonTooltip: `${namespace}:option.1.tooltip`,
},
async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -151,17 +160,24 @@ export const ATrainersTestEncounter: MysteryEncounter =
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.EPIC
tier: EggTier.EPIC,
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]);
setEncounterRewards(
{
guaranteedModifierTypeFuncs: [modifierTypes.SACRED_ASH],
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA],
fillRemaining: true,
},
[eggOptions],
);
await initBattleWithEnemyConfig(config);
}
},
)
.withSimpleOption(
{
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`
buttonTooltip: `${namespace}:option.2.tooltip`,
},
async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -172,16 +188,16 @@ export const ATrainersTestEncounter: MysteryEncounter =
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.RARE
tier: EggTier.RARE,
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`));
setEncounterRewards({ fillRemaining: false, rerollMultiplier: -1 }, [eggOptions]);
leaveEncounterWithoutBattle();
}
},
)
.withOutroDialogue([
{
text: `${namespace}:outro`
}
text: `${namespace}:outro`,
},
])
.build();

View File

@ -1,5 +1,11 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
@ -20,7 +26,11 @@ import { Moves } from "#enums/moves";
import { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils";
import { BattlerIndex } from "#app/battle";
import { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import {
applyModifierTypeToPlayerPokemon,
catchPokemon,
getHighestLevelPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#app/data/trainer-config";
import { PokeballType } from "#enums/pokeball";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
@ -38,8 +48,9 @@ const namespace = "mysteryEncounters/absoluteAvarice";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3805 | GitHub Issue #3805}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const AbsoluteAvariceEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.ABSOLUTE_AVARICE)
export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.ABSOLUTE_AVARICE,
)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Must have at least 4 berries to spawn
@ -53,7 +64,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
hasShadow: true,
alpha: 0.001,
repeat: true,
x: -5
x: -5,
},
{
spriteKey: "",
@ -61,7 +72,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
species: Species.GREEDENT,
hasShadow: false,
repeat: true,
x: -5
x: -5,
},
{
spriteKey: "lum_berry",
@ -70,7 +81,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 7,
y: -14,
hidden: true,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: "salac_berry",
@ -79,7 +90,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 2,
y: 4,
hidden: true,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: "lansat_berry",
@ -88,7 +99,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 32,
y: 5,
hidden: true,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: "liechi_berry",
@ -97,7 +108,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 6,
y: -5,
hidden: true,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: "sitrus_berry",
@ -106,7 +117,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 7,
y: 8,
hidden: true,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: "enigma_berry",
@ -115,7 +126,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 26,
y: -4,
hidden: true,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: "leppa_berry",
@ -124,7 +135,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 16,
y: -27,
hidden: true,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: "petaya_berry",
@ -133,7 +144,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 30,
y: -17,
hidden: true,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: "ganlon_berry",
@ -142,7 +153,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 16,
y: -11,
hidden: true,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: "apicot_berry",
@ -151,7 +162,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 14,
y: -2,
hidden: true,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: "starf_berry",
@ -160,7 +171,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 18,
y: 9,
hidden: true,
disableAnimation: true
disableAnimation: true,
},
])
.withHideWildIntroMessage(true)
@ -168,7 +179,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
.withIntroDialogue([
{
text: `${namespace}:intro`,
}
},
])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
@ -200,7 +211,9 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
// Can't define stack count on a ModifierType, have to just create separate instances for each stack
// Overflow berries will be "lost" on the boss, but it's un-catchable anyway
for (let i = 0; i < berryMod.stackCount; i++) {
const modifierType = generateModifierType(modifierTypes.BERRY, [ berryMod.berryType ]) as PokemonHeldItemModifierType;
const modifierType = generateModifierType(modifierTypes.BERRY, [
berryMod.berryType,
]) as PokemonHeldItemModifierType;
bossModifierConfigs.push({ modifier: modifierType });
}
});
@ -208,9 +221,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
// Do NOT remove the real berries yet or else it will be persisted in the session data
// SpDef buff below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
[ Stat.SPDEF ] :
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
globalScene.currentBattle.waveIndex < 50 ? [Stat.SPDEF] : [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
// Calculate boss mon
const config: EnemyPartyConfig = {
@ -226,9 +238,11 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
}
}
globalScene.unshiftPhase(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1),
);
},
},
],
};
@ -253,8 +267,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
@ -270,7 +283,10 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
// Provides 1x Reviver Seed to each party member at end of battle
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED);
encounter.setDialogueToken("foodReward", revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"));
encounter.setDialogueToken(
"foodReward",
revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"),
);
const givePartyPokemonReviverSeeds = () => {
const party = globalScene.getPlayerParty();
party.forEach(p => {
@ -288,17 +304,16 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY],
move: new PokemonMove(Moves.STUFF_CHEEKS),
ignorePp: true
ignorePp: true,
});
await transitionMysteryEncounterIntroVisuals(true, true, 500);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
@ -318,7 +333,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
const berryTypesAsArray: BerryType[] = [];
stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType)));
const returnedBerryCount = Math.floor((berryTypesAsArray.length ?? 0) * 2 / 5);
const returnedBerryCount = Math.floor(((berryTypesAsArray.length ?? 0) * 2) / 5);
if (returnedBerryCount > 0) {
for (let i = 0; i < returnedBerryCount; i++) {
@ -336,11 +351,10 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
await transitionMysteryEncounterIntroVisuals(true, true, 500);
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
@ -361,14 +375,19 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
// Greedent joins the team, level equal to 2 below highest party member (shiny locked)
const level = getHighestLevelPlayerPokemon(false, true).level - 2;
const greedent = new EnemyPokemon(getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true);
greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ];
greedent.moveset = [
new PokemonMove(Moves.THRASH),
new PokemonMove(Moves.BODY_PRESS),
new PokemonMove(Moves.STUFF_CHEEKS),
new PokemonMove(Moves.SLACK_OFF),
];
greedent.passive = true;
await transitionMysteryEncounterIntroVisuals(true, true, 500);
await catchPokemon(greedent, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.build();
@ -382,70 +401,79 @@ function doGreedentSpriteSteal() {
globalScene.tweens.chain({
targets: greedentSprites,
tweens: [
{ // Slide Greedent diagonally
{
// Slide Greedent diagonally
duration: slideDelay,
ease: "Cubic.easeOut",
y: "+=75",
x: "-=65",
scale: 1.1
scale: 1.1,
},
{ // Shake
{
// Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Shake
{
// Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Shake
{
// Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Shake
{
// Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Shake
{
// Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Shake
{
// Shake
duration: shakeDelay,
ease: "Cubic.easeOut",
yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
},
{ // Slide Greedent diagonally
{
// Slide Greedent diagonally
duration: slideDelay,
ease: "Cubic.easeOut",
y: "-=75",
x: "+=65",
scale: 1
scale: 1,
},
{ // Bounce at the end
{
// Bounce at the end
duration: 300,
ease: "Cubic.easeOut",
yoyo: true,
y: "-=20",
loop: 1,
}
]
},
],
});
}
@ -467,16 +495,28 @@ function doGreedentEatBerries() {
globalScene.playSound("battle_anims/PRSFX- Bug Bite");
}
index++;
}
},
});
}
/**
* @param isEat Default false. Will "create" pile when false, and remove pile when true.
*/
function doBerrySpritePile(isEat: boolean = false) {
function doBerrySpritePile(isEat = false) {
const berryAddDelay = 150;
let animationOrder = [ "starf", "sitrus", "lansat", "salac", "apicot", "enigma", "liechi", "ganlon", "lum", "petaya", "leppa" ];
let animationOrder = [
"starf",
"sitrus",
"lansat",
"salac",
"apicot",
"enigma",
"liechi",
"ganlon",
"lum",
"petaya",
"leppa",
];
if (isEat) {
animationOrder = animationOrder.reverse();
}
@ -515,7 +555,7 @@ function doBerryBounce(berrySprites: Phaser.GameObjects.Sprite[], yd: number, ba
globalScene.tweens.add({
targets: berrySprites,
y: "+=" + bounceYOffset,
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" },
x: { value: "+=" + bouncePower * bouncePower * 10, ease: "Linear" },
duration: bouncePower * baseBounceDuration,
ease: "Cubic.easeIn",
onComplete: () => {
@ -527,13 +567,13 @@ function doBerryBounce(berrySprites: Phaser.GameObjects.Sprite[], yd: number, ba
globalScene.tweens.add({
targets: berrySprites,
y: "-=" + bounceYOffset,
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" },
x: { value: "+=" + bouncePower * bouncePower * 10, ease: "Linear" },
duration: bouncePower * baseBounceDuration,
ease: "Cubic.easeOut",
onComplete: () => doBounce()
onComplete: () => doBounce(),
});
}
}
},
});
};

View File

@ -1,4 +1,9 @@
import { generateModifierType, leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierType,
leaveEncounterWithoutBattle,
setEncounterExp,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
@ -6,7 +11,11 @@ import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import {
AbilityRequirement,
CombinationPokemonRequirement,
MoveRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species";
@ -33,8 +42,9 @@ const MONEY_MAXIMUM_MULTIPLIER = 30;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE)
export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE,
)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
@ -46,7 +56,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
repeat: true,
x: 0,
y: -4,
yShadow: -4
yShadow: -4,
},
{
spriteKey: "rich_kid_m",
@ -54,7 +64,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
hasShadow: true,
x: 2,
y: 5,
yShadow: 5
yShadow: 5,
},
])
.withIntroDialogue([
@ -76,7 +86,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId();
const starterValue: number = speciesStarterCosts[baseSpecies] ?? 1;
const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER);
const multiplier = Math.max((MONEY_MAXIMUM_MULTIPLIER / 10) * starterValue, MONEY_MINIMUM_MULTIPLIER);
const price = globalScene.getWaveMoneyAmount(multiplier);
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
@ -85,7 +95,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
// Store pokemon and price
encounter.misc = {
pokemon: pokemon,
price: price
price: price,
};
// If player meets the combo OR requirements for option 2, populate the token
@ -107,8 +117,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
@ -131,16 +140,15 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM));
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
new MoveRequirement(EXTORTION_MOVES, true),
new AbilityRequirement(EXTORTION_ABILITIES, true)
)
new AbilityRequirement(EXTORTION_ABILITIES, true),
),
)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
@ -163,7 +171,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.withSimpleOption(
{
@ -180,6 +188,6 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.build();

View File

@ -1,6 +1,5 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type {
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierType,
generateModifierTypeOption,
@ -8,18 +7,12 @@ import {
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterExp,
setEncounterRewards
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import type {
BerryModifierType,
ModifierTypeOption } from "#app/modifier/modifier-type";
import {
ModifierPoolType,
modifierTypes,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -30,7 +23,13 @@ import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-enco
import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { applyModifierTypeToPlayerPokemon, getEncounterPokemonLevelForWave, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import {
applyModifierTypeToPlayerPokemon,
getEncounterPokemonLevelForWave,
getHighestStatPlayerPokemon,
getSpriteKeysFromPokemon,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data";
import { BerryModifier } from "#app/modifier/modifier";
import i18next from "#app/plugins/i18n";
@ -47,8 +46,9 @@ const namespace = "mysteryEncounters/berriesAbound";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3810 | GitHub Issue #3810}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const BerriesAboundEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BERRIES_ABOUND)
export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.BERRIES_ABOUND,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withCatchAllowed(true)
@ -68,21 +68,27 @@ export const BerriesAboundEncounter: MysteryEncounter =
const bossPokemon = getRandomEncounterSpecies(level, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = {
pokemonConfigs: [{
pokemonConfigs: [
{
level: level,
species: bossPokemon.species,
dataSource: new PokemonData(bossPokemon),
isBoss: true
}],
isBoss: true,
},
],
};
encounter.enemyPartyConfigs = [config];
// Calculate the number of extra berries that player receives
// 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7
const numBerries =
globalScene.currentBattle.waveIndex > 160 ? 7
: globalScene.currentBattle.waveIndex > 120 ? 5
: globalScene.currentBattle.waveIndex > 40 ? 4 : 2;
globalScene.currentBattle.waveIndex > 160
? 7
: globalScene.currentBattle.waveIndex > 120
? 5
: globalScene.currentBattle.waveIndex > 40
? 4
: 2;
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
encounter.misc = { numBerries };
@ -95,7 +101,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
y: -6,
yShadow: -7,
disableAnimation: true,
hasShadow: true
hasShadow: true,
},
{
spriteKey: spriteKey,
@ -106,8 +112,8 @@ export const BerriesAboundEncounter: MysteryEncounter =
repeat: true,
isPokemon: true,
isShiny: bossPokemon.shiny,
variant: bossPokemon.variant
}
variant: bossPokemon.variant,
},
];
// Get fastest party pokemon for option 2
@ -141,7 +147,12 @@ export const BerriesAboundEncounter: MysteryEncounter =
const berryText = i18next.t(`${namespace}:berries`);
globalScene.playSound("item_fanfare");
queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries }));
queueEncounterMessage(
i18next.t("battle:rewardGainCount", {
modifierName: berryText,
count: numBerries,
}),
);
// Generate a random berry and give it to the first Pokemon with room for it
for (let i = 0; i < numBerries; i++) {
@ -158,16 +169,19 @@ export const BerriesAboundEncounter: MysteryEncounter =
}
}
setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
setEncounterRewards(
{ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false },
undefined,
doBerryRewards,
);
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
}
},
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`
buttonTooltip: `${namespace}:option.2.tooltip`,
})
.withOptionPhase(async () => {
// Pick race for berries
@ -192,7 +206,12 @@ export const BerriesAboundEncounter: MysteryEncounter =
const berryText = i18next.t(`${namespace}:berries`);
globalScene.playSound("item_fanfare");
queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries }));
queueEncounterMessage(
i18next.t("battle:rewardGainCount", {
modifierName: berryText,
count: numBerries,
}),
);
// Generate a random berry and give it to the first Pokemon with room for it
for (let i = 0; i < numBerries; i++) {
@ -201,21 +220,39 @@ export const BerriesAboundEncounter: MysteryEncounter =
};
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
[ Stat.DEF, Stat.SPDEF, Stat.SPD ] :
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
const statChangesForBattle: (
| Stat.ATK
| Stat.DEF
| Stat.SPATK
| Stat.SPDEF
| Stat.SPD
| Stat.ACC
| Stat.EVA
)[] =
globalScene.currentBattle.waveIndex < 50
? [Stat.DEF, Stat.SPDEF, Stat.SPD]
: [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
const config = globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.2.boss_enraged`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
globalScene.unshiftPhase(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1),
);
};
setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
setEncounterRewards(
{
guaranteedModifierTypeOptions: shopOptions,
fillRemaining: false,
},
undefined,
doBerryRewards,
);
await showEncounterText(`${namespace}:option.2.selected_bad`);
await initBattleWithEnemyConfig(config);
return;
} else {
}
// Gains 1 berry for every 10% faster the player's pokemon is than the enemy, up to a max of numBerries, minimum of 2
const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1) / 0.08), numBerries), 2);
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
@ -223,7 +260,12 @@ export const BerriesAboundEncounter: MysteryEncounter =
const berryText = i18next.t(`${namespace}:berries`);
globalScene.playSound("item_fanfare");
queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerriesGrabbed }));
queueEncounterMessage(
i18next.t("battle:rewardGainCount", {
modifierName: berryText,
count: numBerriesGrabbed,
}),
);
// Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first)
for (let i = 0; i < numBerriesGrabbed; i++) {
@ -232,12 +274,18 @@ export const BerriesAboundEncounter: MysteryEncounter =
};
setEncounterExp(fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doFasterBerryRewards);
setEncounterRewards(
{
guaranteedModifierTypeOptions: shopOptions,
fillRemaining: false,
},
undefined,
doFasterBerryRewards,
);
await showEncounterText(`${namespace}:option.2.selected`);
leaveEncounterWithoutBattle();
}
})
.build()
.build(),
)
.withSimpleOption(
{
@ -253,20 +301,25 @@ export const BerriesAboundEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.build();
function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType;
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !Number.isNaN(Number(s))).length) as BerryType;
const berry = generateModifierType(modifierTypes.BERRY, [berryType]) as BerryModifierType;
const party = globalScene.getPlayerParty();
// Will try to apply to prioritized pokemon first, then do normal application method if it fails
if (prioritizedPokemon) {
const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier
&& m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
const heldBerriesOfType = globalScene.findModifier(
m =>
m instanceof BerryModifier &&
m.pokemonId === prioritizedPokemon.id &&
(m as BerryModifier).berryType === berryType,
true,
) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry);
@ -276,8 +329,10 @@ function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
// Iterate over the party until berry was successfully given
for (const pokemon of party) {
const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier
&& m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
const heldBerriesOfType = globalScene.findModifier(
m => m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType,
true,
) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
applyModifierTypeToPlayerPokemon(pokemon, berry);

View File

@ -1,5 +1,4 @@
import type {
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierType,
generateModifierTypeOption,
@ -39,24 +38,22 @@ import {
AttackTypeBoosterHeldItemTypeRequirement,
CombinationPokemonRequirement,
HeldItemRequirement,
TypeRequirement
TypeRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { Type } from "#enums/type";
import { PokemonType } from "#enums/pokemon-type";
import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import type {
PokemonHeldItemModifier
} from "#app/modifier/modifier";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import {
AttackTypeBoosterModifier,
BypassSpeedChanceModifier,
ContactHeldItemTransferChanceModifier,
GigantamaxAccessModifier,
MegaEvolutionAccessModifier
MegaEvolutionAccessModifier,
} from "#app/modifier/modifier";
import i18next from "i18next";
import MoveInfoOverlay from "#app/ui/move-info-overlay";
import { allMoves } from "#app/data/move";
import { allMoves } from "#app/data/moves/move";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
@ -87,7 +84,7 @@ const POOL_1_POKEMON = [
Species.CHARJABUG,
Species.RIBOMBEE,
Species.SPIDOPS,
Species.LOKIX
Species.LOKIX,
];
const POOL_2_POKEMON = [
@ -116,26 +113,26 @@ const POOL_2_POKEMON = [
Species.KLEAVOR,
];
const POOL_3_POKEMON: { species: Species, formIndex?: number }[] = [
const POOL_3_POKEMON: { species: Species; formIndex?: number }[] = [
{
species: Species.PINSIR,
formIndex: 1
formIndex: 1,
},
{
species: Species.SCIZOR,
formIndex: 1
formIndex: 1,
},
{
species: Species.HERACROSS,
formIndex: 1
formIndex: 1,
},
{
species: Species.ORBEETLE,
formIndex: 1
formIndex: 1,
},
{
species: Species.CENTISKORCH,
formIndex: 1
formIndex: 1,
},
{
species: Species.DURANT,
@ -148,35 +145,19 @@ const POOL_3_POKEMON: { species: Species, formIndex?: number }[] = [
},
];
const POOL_4_POKEMON = [
Species.GENESECT,
Species.SLITHER_WING,
Species.BUZZWOLE,
Species.PHEROMOSA
];
const POOL_4_POKEMON = [Species.GENESECT, Species.SLITHER_WING, Species.BUZZWOLE, Species.PHEROMOSA];
const PHYSICAL_TUTOR_MOVES = [
Moves.MEGAHORN,
Moves.X_SCISSOR,
Moves.ATTACK_ORDER,
Moves.PIN_MISSILE,
Moves.FIRST_IMPRESSION
Moves.FIRST_IMPRESSION,
];
const SPECIAL_TUTOR_MOVES = [
Moves.SILVER_WIND,
Moves.BUG_BUZZ,
Moves.SIGNAL_BEAM,
Moves.POLLEN_PUFF
];
const SPECIAL_TUTOR_MOVES = [Moves.SILVER_WIND, Moves.BUG_BUZZ, Moves.SIGNAL_BEAM, Moves.POLLEN_PUFF];
const STATUS_TUTOR_MOVES = [
Moves.STRING_SHOT,
Moves.STICKY_WEB,
Moves.SILK_TRAP,
Moves.RAGE_POWDER,
Moves.HEAL_ORDER
];
const STATUS_TUTOR_MOVES = [Moves.STRING_SHOT, Moves.STICKY_WEB, Moves.SILK_TRAP, Moves.RAGE_POWDER, Moves.HEAL_ORDER];
const MISC_TUTOR_MOVES = [
Moves.BUG_BITE,
@ -185,7 +166,7 @@ const MISC_TUTOR_MOVES = [
Moves.QUIVER_DANCE,
Moves.TAIL_GLOW,
Moves.INFESTATION,
Moves.U_TURN
Moves.U_TURN,
];
/**
@ -198,16 +179,17 @@ const WAVE_LEVEL_BREAKPOINTS = [ 30, 50, 70, 100, 120, 140, 160 ];
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3820 | GitHub Issue #3820}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const BugTypeSuperfanEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BUG_TYPE_SUPERFAN)
export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.BUG_TYPE_SUPERFAN,
)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
// Must have at least 1 Bug type on team, OR have a bug item somewhere on the team
new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1),
new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1),
new TypeRequirement(Type.BUG, false, 1)
)
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1),
new TypeRequirement(PokemonType.BUG, false, 1),
),
)
.withMaxAllowedEncounters(1)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
@ -234,7 +216,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
female: true,
});
let beedrillKeys: { spriteKey: string, fileRoot: string }, butterfreeKeys: { spriteKey: string, fileRoot: string };
let beedrillKeys: { spriteKey: string; fileRoot: string }, butterfreeKeys: { spriteKey: string; fileRoot: string };
if (globalScene.currentBattle.waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) {
beedrillKeys = getSpriteKeysFromSpecies(Species.BEEDRILL, false);
butterfreeKeys = getSpriteKeysFromSpecies(Species.BUTTERFREE, false);
@ -254,7 +236,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
x: -30,
tint: 0.15,
y: -4,
yShadow: -4
yShadow: -4,
},
{
spriteKey: butterfreeKeys.spriteKey,
@ -265,7 +247,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
x: 30,
tint: 0.15,
y: -4,
yShadow: -4
yShadow: -4,
},
{
spriteKey: spriteKey,
@ -273,14 +255,14 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
hasShadow: true,
x: 4,
y: 7,
yShadow: 7
yShadow: 7,
},
];
const requiredItems = [
generateModifierType(modifierTypes.QUICK_CLAW),
generateModifierType(modifierTypes.GRIP_CLAW),
generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.BUG ]),
generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.BUG]),
];
const requiredItemString = requiredItems.map(m => m?.name ?? "unknown").join("/");
@ -315,7 +297,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
moveTutorOptions.push(new PokemonMove(STATUS_TUTOR_MOVES[randSeedInt(STATUS_TUTOR_MOVES.length)]));
moveTutorOptions.push(new PokemonMove(MISC_TUTOR_MOVES[randSeedInt(MISC_TUTOR_MOVES.length)]));
encounter.misc = {
moveTutorOptions
moveTutorOptions,
};
// Assigns callback that teaches move before continuing to rewards
@ -324,27 +306,32 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
setEncounterRewards({ fillRemaining: true });
await transitionMysteryEncounterIntroVisuals(true, true);
await initBattleWithEnemyConfig(config);
}
},
)
.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new TypeRequirement(Type.BUG, false, 1)) // Must have 1 Bug type on team
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new TypeRequirement(PokemonType.BUG, false, 1)) // Must have 1 Bug type on team
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
})
.withPreOptionPhase(async () => {
// Player shows off their bug types
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Player gets different rewards depending on the number of bug types they have
const numBugTypes = globalScene.getPlayerParty().filter(p => p.isOfType(Type.BUG, true)).length;
const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, { count: numBugTypes });
const numBugTypes = globalScene.getPlayerParty().filter(p => p.isOfType(PokemonType.BUG, true)).length;
const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, {
count: numBugTypes,
});
encounter.setDialogueToken("numBugTypes", numBugTypesText);
if (numBugTypes < 2) {
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL],
fillRemaining: false,
});
encounter.selectedOption!.dialogue!.selected = [
{
speaker: `${namespace}:speaker`,
@ -352,7 +339,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
},
];
} else if (numBugTypes < 4) {
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL],
fillRemaining: false,
});
encounter.selectedOption!.dialogue!.selected = [
{
speaker: `${namespace}:speaker`,
@ -360,7 +350,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
},
];
} else if (numBugTypes < 6) {
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL],
fillRemaining: false,
});
encounter.selectedOption!.dialogue!.selected = [
{
speaker: `${namespace}:speaker`,
@ -399,7 +392,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
modifierOptions.push(specialOptions[randSeedInt(specialOptions.length)]);
}
setEncounterRewards({ guaranteedModifierTypeOptions: modifierOptions, fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeOptions: modifierOptions,
fillRemaining: false,
});
encounter.selectedOption!.dialogue!.selected = [
{
speaker: `${namespace}:speaker`,
@ -412,15 +408,16 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
// Player shows off their bug types
leaveEncounterWithoutBattle();
})
.build())
.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.build(),
)
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
// Meets one or both of the below reqs
new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1),
new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1)
)
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1),
),
)
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
@ -443,10 +440,13 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter(item => {
return (item instanceof BypassSpeedChanceModifier ||
return (
(item instanceof BypassSpeedChanceModifier ||
item instanceof ContactHeldItemTransferChanceModifier ||
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === Type.BUG)) &&
item.isTransferable;
(item instanceof AttackTypeBoosterModifier &&
(item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)) &&
item.isTransferable
);
});
return validItems.map((modifier: PokemonHeldItemModifier) => {
@ -469,9 +469,12 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon has valid item, it can be selected
const hasValidItem = pokemon.getHeldItems().some(item => {
return item instanceof BypassSpeedChanceModifier ||
return (
item instanceof BypassSpeedChanceModifier ||
item instanceof ContactHeldItemTransferChanceModifier ||
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === Type.BUG);
(item instanceof AttackTypeBoosterModifier &&
(item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)
);
});
if (!hasValidItem) {
return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null;
@ -493,10 +496,15 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!;
bugNet.type.tier = ModifierTier.ROGUE;
setEncounterRewards({ guaranteedModifierTypeOptions: [ bugNet ], guaranteedModifierTypeFuncs: [ modifierTypes.REVIVER_SEED ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeOptions: [bugNet],
guaranteedModifierTypeFuncs: [modifierTypes.REVIVER_SEED],
fillRemaining: false,
});
leaveEncounterWithoutBattle(true);
})
.build())
.build(),
)
.withOutroDialogue([
{
text: `${namespace}:outro`,
@ -542,108 +550,160 @@ function getTrainerConfigForWave(waveIndex: number) {
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[4]) {
config
.setPartyTemplates(new TrainerPartyTemplate(5, PartyMemberStrength.AVERAGE))
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(
0,
getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1;
p.generateAndPopulateMoveset();
p.generateName();
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true, p => {
}),
)
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1;
p.generateAndPopulateMoveset();
p.generateName();
}))
}),
)
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
.setPartyMemberFunc(3, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ pool3Mon.species ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(
4,
getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset();
p.generateName();
}
}));
}),
);
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[5]) {
pool3Copy = randSeedShuffle(pool3Copy);
const pool3Mon2 = pool3Copy.pop()!;
config
.setPartyTemplates(new TrainerPartyTemplate(5, PartyMemberStrength.AVERAGE))
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(
0,
getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1;
p.generateAndPopulateMoveset();
p.generateName();
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true, p => {
}),
)
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1;
p.generateAndPopulateMoveset();
p.generateName();
}))
}),
)
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ pool3Mon.species ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(
3,
getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset();
p.generateName();
}
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ pool3Mon2.species ], TrainerSlot.TRAINER, true, p => {
}),
)
.setPartyMemberFunc(
4,
getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon2.formIndex)) {
p.formIndex = pool3Mon2.formIndex;
p.generateAndPopulateMoveset();
p.generateName();
}
}));
}),
);
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[6]) {
config
.setPartyTemplates(new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(4, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(1, PartyMemberStrength.STRONG)))
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true, p => {
.setPartyTemplates(
new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(4, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
)
.setPartyMemberFunc(
0,
getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1;
p.generateAndPopulateMoveset();
p.generateName();
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true, p => {
}),
)
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1;
p.generateAndPopulateMoveset();
p.generateName();
}))
}),
)
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ pool3Mon.species ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(
3,
getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset();
p.generateName();
}
}))
}),
)
.setPartyMemberFunc(4, getRandomPartyMemberFunc(POOL_4_POKEMON, TrainerSlot.TRAINER, true));
} else {
pool3Copy = randSeedShuffle(pool3Copy);
const pool3Mon2 = pool3Copy.pop()!;
config
.setPartyTemplates(new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(4, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(1, PartyMemberStrength.STRONG)))
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true, p => {
.setPartyTemplates(
new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(4, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
)
.setPartyMemberFunc(
0,
getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.formIndex = 1;
p.generateAndPopulateMoveset();
p.generateName();
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true, p => {
}),
)
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.formIndex = 1;
p.generateAndPopulateMoveset();
p.generateName();
}))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ pool3Mon.species ], TrainerSlot.TRAINER, true, p => {
}),
)
.setPartyMemberFunc(
2,
getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset();
p.generateName();
}
}))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ pool3Mon2.species ], TrainerSlot.TRAINER, true, p => {
}),
)
.setPartyMemberFunc(
3,
getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon2.formIndex)) {
p.formIndex = pool3Mon2.formIndex;
p.generateAndPopulateMoveset();
p.generateName();
}
}))
}),
)
.setPartyMemberFunc(4, getRandomPartyMemberFunc(POOL_4_POKEMON, TrainerSlot.TRAINER, true));
}
@ -651,6 +711,7 @@ function getTrainerConfigForWave(waveIndex: number) {
}
function doBugTypeMoveTutor(): Promise<void> {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO explain
return new Promise<void>(async resolve => {
const moveOptions = globalScene.currentBattle.mysteryEncounter!.misc.moveTutorOptions;
await showEncounterDialogue(`${namespace}:battle_won`, `${namespace}:speaker`);
@ -663,7 +724,7 @@ function doBugTypeMoveTutor(): Promise<void> {
right: true,
x: 1,
y: -MoveInfoOverlay.getHeight(overlayScale, true) - 1,
width: (globalScene.game.canvas.width / 6) - 2,
width: globalScene.game.canvas.width / 6 - 2,
});
globalScene.ui.add(moveInfoOverlay);
@ -688,7 +749,12 @@ function doBugTypeMoveTutor(): Promise<void> {
moveInfoOverlay.setVisible(false);
};
const result = await selectOptionThenPokemon(optionSelectItems, `${namespace}:teach_move_prompt`, undefined, onHoverOverCancel);
const result = await selectOptionThenPokemon(
optionSelectItems,
`${namespace}:teach_move_prompt`,
undefined,
onHoverOverCancel,
);
// let forceExit = !!result;
if (!result) {
moveInfoOverlay.active = false;
@ -699,7 +765,9 @@ function doBugTypeMoveTutor(): Promise<void> {
// Option select complete, handle if they are learning a move
if (result && result.selectedOptionIndex < moveOptions.length) {
globalScene.unshiftPhase(new LearnMovePhase(result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId));
globalScene.unshiftPhase(
new LearnMovePhase(result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId),
);
}
// Complete battle and go to rewards

View File

@ -1,6 +1,14 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config";
import {
generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
loadCustomMovesForEncounter,
selectPokemonForOption,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type";
@ -14,8 +22,11 @@ import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Abilities } from "#enums/abilities";
import { applyAbilityOverrideToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { Type } from "#enums/type";
import {
applyAbilityOverrideToPokemon,
applyModifierTypeToPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { PokemonType } from "#enums/pokemon-type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { randSeedInt, randSeedShuffle } from "#app/utils";
@ -31,7 +42,7 @@ import { BerryType } from "#enums/berry-type";
import { BattlerIndex } from "#app/battle";
import { Moves } from "#enums/moves";
import { EncounterBattleAnim } from "#app/data/battle-anims";
import { MoveCategory } from "#app/data/move";
import { MoveCategory } from "#enums/MoveCategory";
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { EncounterAnim } from "#enums/encounter-anims";
@ -55,7 +66,7 @@ const RANDOM_ABILITY_POOL = [
Abilities.MISTY_SURGE,
Abilities.MAGICIAN,
Abilities.SHEER_FORCE,
Abilities.PRANKSTER
Abilities.PRANKSTER,
];
/**
@ -63,8 +74,9 @@ const RANDOM_ABILITY_POOL = [
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3807 | GitHub Issue #3807}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ClowningAroundEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.CLOWNING_AROUND,
)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withDisallowedChallenges(Challenges.SINGLE_TYPE)
.withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
@ -79,7 +91,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
x: -25,
tint: 0.3,
y: -3,
yShadow: -3
yShadow: -3,
},
{
spriteKey: Species.BLACEPHALON.toString(),
@ -89,7 +101,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
x: 25,
tint: 0.3,
y: -3,
yShadow: -3
yShadow: -3,
},
{
spriteKey: "harlequin",
@ -97,7 +109,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
hasShadow: true,
x: 0,
y: 2,
yShadow: 2
yShadow: 2,
},
])
.withIntroDialogue([
@ -106,7 +118,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
},
{
text: `${namespace}:intro_dialogue`,
speaker: `${namespace}:speaker`
speaker: `${namespace}:speaker`,
},
])
.withOnInit(() => {
@ -116,7 +128,8 @@ export const ClowningAroundEncounter: MysteryEncounter =
const clownConfig = trainerConfigs[clownTrainerType].clone();
const clownPartyTemplate = new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER));
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
);
clownConfig.setPartyTemplates(clownPartyTemplate);
clownConfig.setDoubleOnly();
// @ts-ignore
@ -136,20 +149,25 @@ export const ClowningAroundEncounter: MysteryEncounter =
encounter.enemyPartyConfigs.push({
trainerConfig: clownConfig,
pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon
pokemonConfigs: [
// Overrides first 2 pokemon to be Mr. Mime and Blacephalon
{
species: getPokemonSpecies(Species.MR_MIME),
isBoss: true,
moveSet: [ Moves.TEETER_DANCE, Moves.ALLY_SWITCH, Moves.DAZZLING_GLEAM, Moves.PSYCHIC ]
moveSet: [Moves.TEETER_DANCE, Moves.ALLY_SWITCH, Moves.DAZZLING_GLEAM, Moves.PSYCHIC],
},
{ // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
{
// Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
species: getPokemonSpecies(Species.BLACEPHALON),
customPokemonData: new CustomPokemonData({ ability: ability, types: [ firstType, secondType ]}),
customPokemonData: new CustomPokemonData({
ability: ability,
types: [firstType, secondType],
}),
isBoss: true,
moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ]
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN],
},
],
doubleBattle: true
doubleBattle: true,
});
// Load animations/sfx for start of fight moves
@ -164,15 +182,14 @@ export const ClowningAroundEncounter: MysteryEncounter =
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [
{
text: `${namespace}:option.1.selected`,
speaker: `${namespace}:speaker`
speaker: `${namespace}:speaker`,
},
],
})
@ -185,24 +202,26 @@ export const ClowningAroundEncounter: MysteryEncounter =
// TODO: when Magic Room and Wonder Room are implemented, add those to start of battle
encounter.startOfBattleEffects.push(
{ // Mr. Mime copies the Blacephalon's random ability
{
// Mr. Mime copies the Blacephalon's random ability
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY_2],
move: new PokemonMove(Moves.ROLE_PLAY),
ignorePp: true
ignorePp: true,
},
{
sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.TAUNT),
ignorePp: true
ignorePp: true,
},
{
sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER_2],
move: new PokemonMove(Moves.TAUNT),
ignorePp: true
});
ignorePp: true,
},
);
await transitionMysteryEncounterIntroVisuals();
await initBattleWithEnemyConfig(config);
@ -222,31 +241,34 @@ export const ClowningAroundEncounter: MysteryEncounter =
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 250
duration: 250,
});
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
const background = new EncounterBattleAnim(
EncounterAnim.SMOKESCREEN,
globalScene.getPlayerPokemon()!,
globalScene.getPlayerPokemon(),
);
background.playWithoutTargets(230, 40, 2);
return true;
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [
{
text: `${namespace}:option.2.selected`,
speaker: `${namespace}:speaker`
speaker: `${namespace}:speaker`,
},
{
text: `${namespace}:option.2.selected_2`,
},
{
text: `${namespace}:option.2.selected_3`,
speaker: `${namespace}:speaker`
speaker: `${namespace}:speaker`,
},
],
})
@ -258,19 +280,21 @@ export const ClowningAroundEncounter: MysteryEncounter =
const party = globalScene.getPlayerParty();
let mostHeldItemsPokemon = party[0];
let count = mostHeldItemsPokemon.getHeldItems()
let count = mostHeldItemsPokemon
.getHeldItems()
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
.reduce((v, m) => v + m.stackCount, 0);
party.forEach(pokemon => {
const nextCount = pokemon.getHeldItems()
for (const pokemon of party) {
const nextCount = pokemon
.getHeldItems()
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
.reduce((v, m) => v + m.stackCount, 0);
if (nextCount > count) {
mostHeldItemsPokemon = pokemon;
count = nextCount;
}
});
}
encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender());
@ -278,11 +302,10 @@ export const ClowningAroundEncounter: MysteryEncounter =
// Shuffles Berries (if they have any)
let numBerries = 0;
items.filter(m => m instanceof BerryModifier)
.forEach(m => {
for (const m of items.filter(m => m instanceof BerryModifier)) {
numBerries += m.stackCount;
globalScene.removeModifier(m);
});
}
generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries");
@ -291,8 +314,8 @@ export const ClowningAroundEncounter: MysteryEncounter =
// And Golden Eggs as Rogue tier
let numUltra = 0;
let numRogue = 0;
items.filter(m => m.isTransferable && !(m instanceof BerryModifier))
.forEach(m => {
for (const m of items.filter(m => m.isTransferable && !(m instanceof BerryModifier))) {
const type = m.type.withTierFromPool(ModifierPoolType.PLAYER, party);
const tier = type.tier ?? ModifierTier.ULTRA;
if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
@ -302,7 +325,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
numUltra += m.stackCount;
globalScene.removeModifier(m);
}
});
}
generateItemsOfTier(mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
@ -312,29 +335,32 @@ export const ClowningAroundEncounter: MysteryEncounter =
})
.withPostOptionPhase(async () => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
const background = new EncounterBattleAnim(
EncounterAnim.SMOKESCREEN,
globalScene.getPlayerPokemon()!,
globalScene.getPlayerPokemon(),
);
background.playWithoutTargets(230, 40, 2);
await transitionMysteryEncounterIntroVisuals(true, true, 200);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [
{
text: `${namespace}:option.3.selected`,
speaker: `${namespace}:speaker`
speaker: `${namespace}:speaker`,
},
{
text: `${namespace}:option.3.selected_2`,
},
{
text: `${namespace}:option.3.selected_3`,
speaker: `${namespace}:speaker`
speaker: `${namespace}:speaker`,
},
],
})
@ -347,20 +373,23 @@ export const ClowningAroundEncounter: MysteryEncounter =
// If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type
// Makes the "randomness" of the shuffle slightly less punishing
let priorityTypes = pokemon.moveset
.filter(move => move && !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS)
.filter(
move =>
move && !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS,
)
.map(move => move!.getMove().type);
if (priorityTypes?.length > 0) {
priorityTypes = [...new Set(priorityTypes)].sort();
priorityTypes = randSeedShuffle(priorityTypes);
}
const newTypes = [ Type.UNKNOWN ];
let secondType: Type | null = null;
const newTypes = [PokemonType.UNKNOWN];
let secondType: PokemonType | null = null;
while (secondType === null || secondType === newTypes[0] || originalTypes.includes(secondType)) {
if (priorityTypes.length > 0) {
secondType = priorityTypes.pop() ?? null;
} else {
secondType = randSeedInt(18) as Type;
secondType = randSeedInt(18) as PokemonType;
}
}
newTypes.push(secondType);
@ -383,11 +412,15 @@ export const ClowningAroundEncounter: MysteryEncounter =
})
.withPostOptionPhase(async () => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
const background = new EncounterBattleAnim(
EncounterAnim.SMOKESCREEN,
globalScene.getPlayerPokemon()!,
globalScene.getPlayerPokemon(),
);
background.playWithoutTargets(230, 40, 2);
await transitionMysteryEncounterIntroVisuals(true, true, 200);
})
.build()
.build(),
)
.withOutroDialogue([
{
@ -397,6 +430,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
.build();
async function handleSwapAbility() {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
return new Promise<boolean>(async resolve => {
await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`);
await showEncounterText(`${namespace}:option.1.apply_ability_message`);
@ -415,21 +449,21 @@ function displayYesNoOptions(resolve) {
handler: () => {
onYesAbilitySwap(resolve);
return true;
}
},
},
{
label: i18next.t("menu:no"),
handler: () => {
resolve(false);
return true;
}
}
},
},
];
const config: OptionSelectConfig = {
options: fullOptions,
maxOptions: 7,
yOffset: 0
yOffset: 0,
};
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
}
@ -462,7 +496,7 @@ function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: Mod
[modifierTypes.GOLDEN_PUNCH, 5],
[modifierTypes.ATTACK_TYPE_BOOSTER, 99],
[modifierTypes.QUICK_CLAW, 3],
[ modifierTypes.WIDE_LENS, 3 ]
[modifierTypes.WIDE_LENS, 3],
];
const roguePool = [
@ -473,7 +507,7 @@ function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: Mod
[modifierTypes.BATON, 1],
[modifierTypes.FOCUS_BAND, 5],
[modifierTypes.KINGS_ROCK, 3],
[ modifierTypes.GRIP_CLAW, 5 ]
[modifierTypes.GRIP_CLAW, 5],
];
const berryPool = [
@ -487,7 +521,7 @@ function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: Mod
[BerryType.PETAYA, 3],
[BerryType.SALAC, 2],
[BerryType.SITRUS, 2],
[ BerryType.STARF, 3 ]
[BerryType.STARF, 3],
];
let pool: any[];

View File

@ -8,8 +8,17 @@ import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-
import { DANCING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { catchPokemon, getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import {
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
catchPokemon,
getEncounterPokemonLevelForWave,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { TrainerSlot } from "#app/data/trainer-config";
import type { PlayerPokemon } from "#app/field/pokemon";
@ -44,7 +53,7 @@ const BAILE_STYLE_BIOMES = [
Biome.WASTELAND,
Biome.MOUNTAIN,
Biome.BADLANDS,
Biome.DESERT
Biome.DESERT,
];
// Electric form
@ -55,7 +64,7 @@ const POM_POM_STYLE_BIOMES = [
Biome.LABORATORY,
Biome.SLUM,
Biome.METROPOLIS,
Biome.DOJO
Biome.DOJO,
];
// Psychic form
@ -66,7 +75,7 @@ const PAU_STYLE_BIOMES = [
Biome.PLAINS,
Biome.GRASS,
Biome.TALL_GRASS,
Biome.FOREST
Biome.FOREST,
];
// Ghost form
@ -77,7 +86,7 @@ const SENSU_STYLE_BIOMES = [
Biome.ABYSS,
Biome.GRAVEYARD,
Biome.LAKE,
Biome.TEMPLE
Biome.TEMPLE,
];
/**
@ -85,8 +94,9 @@ const SENSU_STYLE_BIOMES = [
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3823 | GitHub Issue #3823}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DancingLessonsEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DANCING_LESSONS)
export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.DANCING_LESSONS,
)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // Uses a real Pokemon sprite instead of ME Intro Visuals
@ -108,7 +118,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
.withIntroDialogue([
{
text: `${namespace}:intro`,
}
},
])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
@ -147,9 +157,9 @@ export const DancingLessonsEncounter: MysteryEncounter =
const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
// Adds a real Pokemon sprite to the field (required for the animation)
globalScene.getEnemyParty().forEach(enemyPokemon => {
for (const enemyPokemon of globalScene.getEnemyParty()) {
enemyPokemon.leaveField(true, true, true);
});
}
globalScene.currentBattle.enemyParty = [oricorio];
globalScene.field.add(oricorio);
// Spawns on offscreen field
@ -157,7 +167,8 @@ export const DancingLessonsEncounter: MysteryEncounter =
encounter.loadAssets.push(oricorio.loadAssets());
const config: EnemyPartyConfig = {
pokemonConfigs: [{
pokemonConfigs: [
{
species: species,
dataSource: oricorioData,
isBoss: true,
@ -165,13 +176,21 @@ export const DancingLessonsEncounter: MysteryEncounter =
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF ], 1));
}
}],
globalScene.unshiftPhase(
new StatStageChangePhase(
pokemon.getBattlerIndex(),
true,
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF],
1,
),
);
},
},
],
};
encounter.enemyPartyConfigs = [config];
encounter.misc = {
oricorioData
oricorioData,
};
encounter.setDialogueToken("oricorioName", getPokemonSpecies(Species.ORICORIO).getName());
@ -179,8 +198,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
@ -198,18 +216,20 @@ export const DancingLessonsEncounter: MysteryEncounter =
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.REVELATION_DANCE),
ignorePp: true
ignorePp: true,
});
await hideOricorioPokemon();
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.BATON ], fillRemaining: true });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.BATON],
fillRemaining: true,
});
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
@ -225,10 +245,16 @@ export const DancingLessonsEncounter: MysteryEncounter =
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(pokemon), Moves.REVELATION_DANCE));
globalScene.unshiftPhase(
new LearnMovePhase(globalScene.getPlayerParty().indexOf(pokemon), Moves.REVELATION_DANCE),
);
// Play animation again to "learn" the dance
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, globalScene.getEnemyPokemon()!, globalScene.getPlayerPokemon());
const danceAnim = new EncounterBattleAnim(
EncounterAnim.DANCE,
globalScene.getEnemyPokemon()!,
globalScene.getPlayerPokemon(),
);
danceAnim.play();
};
@ -239,11 +265,10 @@ export const DancingLessonsEncounter: MysteryEncounter =
await hideOricorioPokemon();
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
@ -283,7 +308,11 @@ export const DancingLessonsEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon meets primary pokemon reqs, it can be selected
if (!pokemon.isAllowedInBattle()) {
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
return (
i18next.t("partyUiHandler:cantBeUsed", {
pokemonName: pokemon.getNameToRender(),
}) ?? null
);
}
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon);
if (!meetsReqs) {
@ -315,7 +344,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
await catchPokemon(oricorio, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.build();
@ -332,7 +361,7 @@ function hideOricorioPokemon() {
onComplete: () => {
globalScene.field.remove(oricorioSprite, true);
resolve();
}
},
});
});
}

View File

@ -1,4 +1,4 @@
import type { Type } from "#enums/type";
import type { PokemonType } from "#enums/pokemon-type";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
@ -9,8 +9,11 @@ import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounte
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
import { getRandomPlayerPokemon, getRandomSpeciesByStarterCost } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle } from "../utils/encounter-phase-utils";
import {
getRandomPlayerPokemon,
getRandomSpeciesByStarterCost,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
@ -93,8 +96,9 @@ const excludedBosses = [
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3806 | GitHub Issue #3806}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DarkDealEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DARK_DEAL)
export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.DARK_DEAL,
)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withIntroSpriteConfigs([
{
@ -126,8 +130,7 @@ export const DarkDealEncounter: MysteryEncounter =
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
@ -156,7 +159,7 @@ export const DarkDealEncounter: MysteryEncounter =
// Store removed pokemon types
encounter.misc = {
removedTypes: removedPokemon.getTypes(),
modifiers
modifiers,
};
})
.withOptionPhase(async () => {
@ -166,17 +169,18 @@ export const DarkDealEncounter: MysteryEncounter =
// Start encounter with random legendary (7-10 starter strength) that has level additive
// If this is a mono-type challenge, always ensure the required type is filtered for
let bossTypes: Type[] = encounter.misc.removedTypes;
const singleTypeChallenges = globalScene.gameMode.challenges.filter(c => c.value && c.id === Challenges.SINGLE_TYPE);
let bossTypes: PokemonType[] = encounter.misc.removedTypes;
const singleTypeChallenges = globalScene.gameMode.challenges.filter(
c => c.value && c.id === Challenges.SINGLE_TYPE,
);
if (globalScene.gameMode.isChallenge && singleTypeChallenges.length > 0) {
bossTypes = singleTypeChallenges.map(c => (c.value - 1) as Type);
bossTypes = singleTypeChallenges.map(c => (c.value - 1) as PokemonType);
}
const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers;
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
const roll = randSeedInt(100);
const starterTier: number | [number, number] =
roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [ 9, 10 ];
const starterTier: number | [number, number] = roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [9, 10];
const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterCost(starterTier, excludedBosses, bossTypes));
const pokemonConfig: EnemyPokemonConfig = {
species: bossSpecies,
@ -186,7 +190,7 @@ export const DarkDealEncounter: MysteryEncounter =
modifier: m,
stackCount: m.getStackCount(),
};
})
}),
};
if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) {
pokemonConfig.formIndex = 0;
@ -196,7 +200,7 @@ export const DarkDealEncounter: MysteryEncounter =
};
await initBattleWithEnemyConfig(config);
})
.build()
.build(),
)
.withSimpleOption(
{
@ -213,11 +217,11 @@ export const DarkDealEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.withOutroDialogue([
{
text: `${namespace}:outro`
}
text: `${namespace}:outro`,
},
])
.build();

View File

@ -2,16 +2,31 @@ import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import {
CombinationPokemonRequirement,
HeldItemRequirement,
MoneyRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierType,
leaveEncounterWithoutBattle,
selectPokemonForOption,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PreserveBerryModifier } from "#app/modifier/modifier";
import {
BerryModifier,
HealingBoosterModifier,
LevelIncrementBoosterModifier,
MoneyMultiplierModifier,
PreserveBerryModifier,
} from "#app/modifier/modifier";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
@ -35,7 +50,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [
"PokemonInstantReviveModifier",
"TerastallizeModifier",
"PokemonBaseStatModifier",
"PokemonBaseStatTotalModifier"
"PokemonBaseStatTotalModifier",
];
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
@ -43,11 +58,11 @@ const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
const doEventReward = () => {
const event_buff = globalScene.eventManager.getDelibirdyBuff();
if (event_buff.length > 0) {
const candidates = event_buff.filter((c => {
const candidates = event_buff.filter(c => {
const mtype = generateModifierType(modifierTypes[c]);
const existingCharm = globalScene.findModifier(m => m.type.id === mtype?.id);
return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount());
}));
});
if (candidates.length > 0) {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes[randSeedItem(candidates)]));
} else {
@ -62,8 +77,9 @@ const doEventReward = () => {
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3804 | GitHub Issue #3804}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DelibirdyEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY)
export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.DELIBIRDY,
)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least
@ -71,8 +87,8 @@ export const DelibirdyEncounter: MysteryEncounter =
CombinationPokemonRequirement.Some(
// Must also have either option 2 or 3 available to spawn
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true)
)
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true),
),
)
.withIntroSpriteConfigs([
{
@ -82,7 +98,7 @@ export const DelibirdyEncounter: MysteryEncounter =
hasShadow: true,
repeat: true,
startFrame: 38,
scale: 0.94
scale: 0.94,
},
{
spriteKey: "",
@ -90,7 +106,7 @@ export const DelibirdyEncounter: MysteryEncounter =
species: Species.DELIBIRD,
hasShadow: true,
repeat: true,
scale: 1.06
scale: 1.06,
},
{
spriteKey: "",
@ -101,13 +117,13 @@ export const DelibirdyEncounter: MysteryEncounter =
startFrame: 65,
x: 1,
y: 5,
yShadow: 5
yShadow: 5,
},
])
.withIntroDialogue([
{
text: `${namespace}:intro`,
}
},
])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
@ -116,7 +132,7 @@ export const DelibirdyEncounter: MysteryEncounter =
.withOutroDialogue([
{
text: `${namespace}:outro`,
}
},
])
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -130,8 +146,7 @@ export const DelibirdyEncounter: MysteryEncounter =
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER) // Must have money to spawn
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
@ -157,7 +172,12 @@ export const DelibirdyEncounter: MysteryEncounter =
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
await showEncounterText(
i18next.t("battle:rewardGain", { modifierName: shellBell.name }),
null,
undefined,
true,
);
doEventReward();
} else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.AMULET_COIN));
@ -166,11 +186,10 @@ export const DelibirdyEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS))
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
@ -186,7 +205,7 @@ export const DelibirdyEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => {
const validItems = pokemon.getHeldItems().filter(it => {
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
});
@ -227,14 +246,23 @@ export const DelibirdyEncounter: MysteryEncounter =
// Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed
if (modifier instanceof BerryModifier) {
// Check if the player has max stacks of that Candy Jar already
const existing = globalScene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier;
const existing = globalScene.findModifier(
m => m instanceof LevelIncrementBoosterModifier,
) as LevelIncrementBoosterModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
await showEncounterText(
i18next.t("battle:rewardGain", {
modifierName: shellBell.name,
}),
null,
undefined,
true,
);
doEventReward();
} else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.CANDY_JAR));
@ -249,7 +277,14 @@ export const DelibirdyEncounter: MysteryEncounter =
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
await showEncounterText(
i18next.t("battle:rewardGain", {
modifierName: shellBell.name,
}),
null,
undefined,
true,
);
doEventReward();
} else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.BERRY_POUCH));
@ -261,11 +296,10 @@ export const DelibirdyEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true))
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
@ -281,8 +315,10 @@ export const DelibirdyEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => {
return !OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
const validItems = pokemon.getHeldItems().filter(it => {
return (
!OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable
);
});
return validItems.map((modifier: PokemonHeldItemModifier) => {
@ -327,7 +363,12 @@ export const DelibirdyEncounter: MysteryEncounter =
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerParty()[0], shellBell);
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
await showEncounterText(
i18next.t("battle:rewardGain", { modifierName: shellBell.name }),
null,
undefined,
true,
);
doEventReward();
} else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.HEALING_CHARM));
@ -338,6 +379,6 @@ export const DelibirdyEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.build();

View File

@ -8,9 +8,7 @@ import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import {
MysteryEncounterBuilder,
} from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
@ -22,8 +20,9 @@ const namespace = "mysteryEncounters/departmentStoreSale";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3797 | GitHub Issue #3797}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DepartmentStoreSaleEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE)
export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.DEPARTMENT_STORE_SALE,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
.withIntroSpriteConfigs([
@ -78,9 +77,12 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
i++;
}
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
setEncounterRewards({
guaranteedModifierTypeFuncs: modifiers,
fillRemaining: false,
});
leaveEncounterWithoutBattle();
}
},
)
.withSimpleOption(
{
@ -102,9 +104,12 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
i++;
}
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
setEncounterRewards({
guaranteedModifierTypeFuncs: modifiers,
fillRemaining: false,
});
leaveEncounterWithoutBattle();
}
},
)
.withSimpleOption(
{
@ -126,9 +131,12 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
i++;
}
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
setEncounterRewards({
guaranteedModifierTypeFuncs: modifiers,
fillRemaining: false,
});
leaveEncounterWithoutBattle();
}
},
)
.withSimpleOption(
{
@ -154,13 +162,16 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
i++;
}
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
setEncounterRewards({
guaranteedModifierTypeFuncs: modifiers,
fillRemaining: false,
});
leaveEncounterWithoutBattle();
}
},
)
.withOutroDialogue([
{
text: `${namespace}:outro`,
}
},
])
.build();

View File

@ -1,6 +1,12 @@
import { MoveCategory } from "#app/data/move";
import { MoveCategory } from "#enums/MoveCategory";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierTypeOption,
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterExp,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
@ -22,8 +28,9 @@ const namespace = "mysteryEncounters/fieldTrip";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3794 | GitHub Issue #3794}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FieldTripEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP)
export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.FIELD_TRIP,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
.withIntroSpriteConfigs([
@ -58,8 +65,7 @@ export const FieldTripEncounter: MysteryEncounter =
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
@ -96,16 +102,18 @@ export const FieldTripEncounter: MysteryEncounter =
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
];
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeOptions: modifiers,
fillRemaining: false,
});
}
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
@ -142,16 +150,18 @@ export const FieldTripEncounter: MysteryEncounter =
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
];
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeOptions: modifiers,
fillRemaining: false,
});
}
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
@ -188,12 +198,15 @@ export const FieldTripEncounter: MysteryEncounter =
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
];
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeOptions: modifiers,
fillRemaining: false,
});
}
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
})
.build()
.build(),
)
.build();
@ -215,7 +228,10 @@ function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correct
text: `${namespace}:incorrect_exp`,
},
];
setEncounterExp(globalScene.getPlayerParty().map((p) => p.id), 50);
setEncounterExp(
globalScene.getPlayerParty().map(p => p.id),
50,
);
} else {
encounter.selectedOption!.dialogue!.selected = [
{

View File

@ -1,17 +1,29 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
initBattleWithEnemyConfig,
loadCustomMovesForEncounter,
leaveEncounterWithoutBattle,
setEncounterExp,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
generateModifierType,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { modifierTypes, } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { AbilityRequirement, CombinationPokemonRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import {
AbilityRequirement,
CombinationPokemonRequirement,
TypeRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { Species } from "#enums/species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Gender } from "#app/data/gender";
import { Type } from "#enums/type";
import { PokemonType } from "#enums/pokemon-type";
import { BattlerIndex } from "#app/battle";
import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
@ -21,7 +33,11 @@ import { WeatherType } from "#enums/weather-type";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { StatusEffect } from "#enums/status-effect";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyAbilityOverrideToPokemon, applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import {
applyAbilityOverrideToPokemon,
applyDamageToPokemon,
applyModifierTypeToPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { EncounterAnim } from "#enums/encounter-anims";
@ -48,8 +64,9 @@ const DAMAGE_PERCENTAGE: number = 20;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3814 | GitHub Issue #3814}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FieryFalloutEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIERY_FALLOUT)
export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.FIERY_FALLOUT,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(40, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withCatchAllowed(true)
@ -75,8 +92,10 @@ export const FieryFalloutEncounter: MysteryEncounter =
gender: Gender.MALE,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1));
}
globalScene.unshiftPhase(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1),
);
},
},
{
species: volcaronaSpecies,
@ -84,9 +103,11 @@ export const FieryFalloutEncounter: MysteryEncounter =
gender: Gender.FEMALE,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1));
}
}
globalScene.unshiftPhase(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1),
);
},
},
],
doubleBattle: true,
disableSwitch: true,
@ -103,7 +124,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
hidden: true,
hasShadow: true,
x: -20,
startFrame: 20
startFrame: 20,
},
{
spriteKey: "",
@ -112,7 +133,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
repeat: true,
hidden: true,
hasShadow: true,
x: 20
x: 20,
},
];
@ -127,9 +148,17 @@ export const FieryFalloutEncounter: MysteryEncounter =
})
.withOnVisualsStart(() => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
const background = new EncounterBattleAnim(
EncounterAnim.MAGMA_BG,
globalScene.getPlayerPokemon()!,
globalScene.getPlayerPokemon(),
);
background.playWithoutTargets(200, 70, 2, 3);
const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
const animation = new EncounterBattleAnim(
EncounterAnim.MAGMA_SPOUT,
globalScene.getPlayerPokemon()!,
globalScene.getPlayerPokemon(),
);
animation.playWithoutTargets(80, 100, 2);
globalScene.time.delayedCall(600, () => {
animation.playWithoutTargets(-20, 100, 2);
@ -164,16 +193,17 @@ export const FieryFalloutEncounter: MysteryEncounter =
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.FIRE_SPIN),
ignorePp: true
ignorePp: true,
},
{
sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER_2],
move: new PokemonMove(Moves.FIRE_SPIN),
ignorePp: true
});
ignorePp: true,
},
);
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
}
},
)
.withSimpleOption(
{
@ -188,7 +218,9 @@ export const FieryFalloutEncounter: MysteryEncounter =
async () => {
// Damage non-fire types and burn 1 random non-fire type member + give it Heatproof
const encounter = globalScene.currentBattle.mysteryEncounter!;
const nonFireTypes = globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE));
const nonFireTypes = globalScene
.getPlayerParty()
.filter(p => p.isAllowedInBattle() && !p.getTypes().includes(PokemonType.FIRE));
for (const pkm of nonFireTypes) {
const percentage = DAMAGE_PERCENTAGE / 100;
@ -197,7 +229,9 @@ export const FieryFalloutEncounter: MysteryEncounter =
}
// Burn random member
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status.effect) || p.status.effect === StatusEffect.NONE);
const burnable = nonFireTypes.filter(
p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status.effect) || p.status.effect === StatusEffect.NONE,
);
if (burnable?.length > 0) {
const roll = randSeedInt(burnable.length);
const chosenPokemon = burnable[roll];
@ -214,16 +248,15 @@ export const FieryFalloutEncounter: MysteryEncounter =
// No rewards
leaveEncounterWithoutBattle(true);
}
},
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
new TypeRequirement(Type.FIRE, true, 1),
new AbilityRequirement(FIRE_RESISTANT_ABILITIES, true)
)
new TypeRequirement(PokemonType.FIRE, true, 1),
new AbilityRequirement(FIRE_RESISTANT_ABILITIES, true),
),
) // Will set option3PrimaryName dialogue token automatically
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
@ -243,10 +276,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
// Fire types help calm the Volcarona
const encounter = globalScene.currentBattle.mysteryEncounter!;
await transitionMysteryEncounterIntroVisuals();
setEncounterRewards(
{ fillRemaining: true },
undefined,
() => {
setEncounterRewards({ fillRemaining: true }, undefined, () => {
giveLeadPokemonAttackTypeBoostItem();
});
@ -255,7 +285,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
setEncounterExp([primary.id], getPokemonSpecies(Species.VOLCARONA).baseExp * 2);
leaveEncounterWithoutBattle();
})
.build()
.build(),
)
.build();
@ -266,7 +296,9 @@ function giveLeadPokemonAttackTypeBoostItem() {
// Generate type booster held item, default to Charcoal if item fails to generate
let boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType;
if (!boosterModifierType) {
boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType;
boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
PokemonType.FIRE,
]) as AttackTypeBoosterModifierType;
}
applyModifierTypeToPlayerPokemon(leadPokemon, boosterModifierType);

View File

@ -1,18 +1,16 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type {
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
getRandomEncounterSpecies,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterExp,
setEncounterRewards
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import type Pokemon from "#app/field/pokemon";
import { ModifierTier } from "#app/modifier/modifier-tier";
import type {
ModifierTypeOption } from "#app/modifier/modifier-type";
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import {
getPlayerModifierTypeOptions,
ModifierPoolType,
@ -25,7 +23,11 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { getEncounterPokemonLevelForWave, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import {
getEncounterPokemonLevelForWave,
getSpriteKeysFromPokemon,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data";
import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -41,8 +43,9 @@ const namespace = "mysteryEncounters/fightOrFlight";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3795 | GitHub Issue #3795}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FightOrFlightEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT)
export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.FIGHT_OR_FLIGHT,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withCatchAllowed(true)
@ -62,7 +65,8 @@ export const FightOrFlightEncounter: MysteryEncounter =
const bossPokemon = getRandomEncounterSpecies(level, true);
encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender());
const config: EnemyPartyConfig = {
pokemonConfigs: [{
pokemonConfigs: [
{
level: level,
species: bossPokemon.species,
dataSource: new PokemonData(bossPokemon),
@ -73,8 +77,9 @@ export const FightOrFlightEncounter: MysteryEncounter =
// Randomly boost 1 stat 2 stages
// Cannot boost Spd, Acc, or Evasion
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2));
}
}],
},
},
],
};
encounter.enemyPartyConfigs = [config];
@ -92,7 +97,10 @@ export const FightOrFlightEncounter: MysteryEncounter =
let item: ModifierTypeOption | null = null;
// TMs and Candy Jar excluded from possible rewards as they're too swingy in value for a singular item reward
while (!item || item.type.id.includes("TM_") || item.type.id === "CANDY_JAR") {
item = getPlayerModifierTypeOptions(1, globalScene.getPlayerParty(), [], { guaranteedModifierTiers: [ tier ], allowLuckUpgrades: false })[0];
item = getPlayerModifierTypeOptions(1, globalScene.getPlayerParty(), [], {
guaranteedModifierTiers: [tier],
allowLuckUpgrades: false,
})[0];
}
encounter.setDialogueToken("itemName", item.type.name);
encounter.misc = item;
@ -107,7 +115,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
y: -5,
scale: 0.75,
isItem: true,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: spriteKey,
@ -118,7 +126,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
repeat: true,
isPokemon: true,
isShiny: bossPokemon.shiny,
variant: bossPokemon.variant
variant: bossPokemon.variant,
},
];
@ -142,13 +150,15 @@ export const FightOrFlightEncounter: MysteryEncounter =
// Pick battle
// Pokemon will randomly boost 1 stat by 2 stages
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeOptions: [item],
fillRemaining: false,
});
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
}
},
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
@ -156,22 +166,25 @@ export const FightOrFlightEncounter: MysteryEncounter =
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
selected: [
{
text: `${namespace}:option.2.selected`
}
]
text: `${namespace}:option.2.selected`,
},
],
})
.withOptionPhase(async () => {
// Pick steal
const encounter = globalScene.currentBattle.mysteryEncounter!;
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeOptions: [item],
fillRemaining: false,
});
// Use primaryPokemon to execute the thievery
const primaryPokemon = encounter.options[1].primaryPokemon!;
setEncounterExp(primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
leaveEncounterWithoutBattle();
})
.build()
.build(),
)
.withSimpleOption(
{
@ -187,6 +200,6 @@ export const FightOrFlightEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.build();

View File

@ -1,4 +1,10 @@
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -35,8 +41,9 @@ const namespace = "mysteryEncounters/funAndGames";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3819 | GitHub Issue #3819}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FunAndGamesEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FUN_AND_GAMES)
export const FunAndGamesEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.FUN_AND_GAMES,
)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion to play
@ -60,7 +67,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
hasShadow: true,
x: -28,
y: 6,
yShadow: 6
yShadow: 6,
},
{
spriteKey: "fun_and_games_man",
@ -68,7 +75,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
hasShadow: true,
x: 40,
y: 6,
yShadow: 6
yShadow: 6,
},
])
.withIntroDialogue([
@ -91,8 +98,8 @@ export const FunAndGamesEncounter: MysteryEncounter =
globalScene.fadeAndSwitchBgm("mystery_encounter_fun_and_games");
return true;
})
.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
@ -127,7 +134,11 @@ export const FunAndGamesEncounter: MysteryEncounter =
// Update money
const moneyCost = (encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney;
updatePlayerMoney(-moneyCost, true, false);
await showEncounterText(i18next.t("mysteryEncounterMessages:paid_money", { amount: moneyCost }));
await showEncounterText(
i18next.t("mysteryEncounterMessages:paid_money", {
amount: moneyCost,
}),
);
// Handlers for battle events
encounter.onTurnStart = handleNextTurn; // triggered during TurnInitPhase
@ -139,7 +150,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
return true;
})
.build()
.build(),
)
.withSimpleOption(
{
@ -156,11 +167,12 @@ export const FunAndGamesEncounter: MysteryEncounter =
await transitionMysteryEncounterIntroVisuals(true, true);
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.build();
async function summonPlayerPokemon() {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
return new Promise<void>(async resolve => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -176,9 +188,15 @@ async function summonPlayerPokemon() {
// Do trainer summon animation
let playerAnimationPromise: Promise<void> | undefined;
globalScene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(playerPokemon) }));
globalScene.ui.showText(
i18next.t("battle:playerGo", {
pokemonName: getPokemonNameWithAffix(playerPokemon),
}),
);
globalScene.pbTray.hide();
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
globalScene.trainer.setTexture(
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`,
);
globalScene.time.delayedCall(562, () => {
globalScene.trainer.setFrame("2");
globalScene.time.delayedCall(64, () => {
@ -189,7 +207,7 @@ async function summonPlayerPokemon() {
targets: globalScene.trainer,
x: -36,
duration: 1000,
onComplete: () => globalScene.trainer.setVisible(false)
onComplete: () => globalScene.trainer.setVisible(false),
});
globalScene.time.delayedCall(750, () => {
playerAnimationPromise = summonPlayerPokemonAnimation(playerPokemon);
@ -198,7 +216,13 @@ async function summonPlayerPokemon() {
// Also loads Wobbuffet data (cannot be shiny)
const enemySpecies = getPokemonSpecies(Species.WOBBUFFET);
globalScene.currentBattle.enemyParty = [];
const wobbuffet = globalScene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false, true);
const wobbuffet = globalScene.addEnemyPokemon(
enemySpecies,
encounter.misc.playerPokemon.level,
TrainerSlot.NONE,
false,
true,
);
wobbuffet.ivs = [0, 0, 0, 0, 0, 0];
wobbuffet.setNature(Nature.MILD);
wobbuffet.setAlpha(0);
@ -219,6 +243,7 @@ async function summonPlayerPokemon() {
}
function handleLoseMinigame() {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
return new Promise<void>(async resolve => {
// Check Wobbuffet is still alive
const wobbuffet = globalScene.getEnemyPokemon();
@ -258,15 +283,24 @@ function handleNextTurn() {
let isHealPhase = false;
if (healthRatio < 0.03) {
// Grand prize
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.MULTI_LENS ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.MULTI_LENS],
fillRemaining: false,
});
resultMessageKey = `${namespace}:best_result`;
} else if (healthRatio < 0.15) {
// 2nd prize
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SCOPE_LENS ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.SCOPE_LENS],
fillRemaining: false,
});
resultMessageKey = `${namespace}:great_result`;
} else if (healthRatio < 0.33) {
// 3rd prize
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.WIDE_LENS ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.WIDE_LENS],
fillRemaining: false,
});
resultMessageKey = `${namespace}:good_result`;
} else {
// No prize
@ -286,14 +320,13 @@ function handleNextTurn() {
// Skip remainder of TurnInitPhase
return true;
} else {
}
if (encounter.misc.turnsRemaining < 3) {
// Display charging messages on turns that aren't the initial turn
queueEncounterMessage(`${namespace}:charging_continue`);
}
queueEncounterMessage(`${namespace}:turn_remaining_${encounter.misc.turnsRemaining}`);
encounter.misc.turnsRemaining--;
}
// Don't skip remainder of TurnInitPhase
return false;
@ -336,7 +369,7 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
globalScene.tweens.add({
targets: pokeball,
duration: 650,
x: 100 + fpOffset[0]
x: 100 + fpOffset[0],
});
globalScene.tweens.add({
@ -387,11 +420,11 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
globalScene.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex()));
resolve();
});
}
},
});
}
},
});
}
},
});
});
}
@ -408,7 +441,7 @@ function hideShowmanIntroSprite() {
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 750
duration: 750,
});
// Slide the Wobbuffet and Game over slightly
@ -416,6 +449,6 @@ function hideShowmanIntroSprite() {
targets: [wobbuffet, carnivalGame],
x: "+=16",
ease: "Sine.easeInOut",
duration: 750
duration: 750,
});
}

View File

@ -1,9 +1,17 @@
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { TrainerSlot, } from "#app/data/trainer-config";
import {
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { TrainerSlot } from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { MusicPreference } from "#app/system/settings/settings";
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import { getPlayerModifierTypeOptions, ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import {
getPlayerModifierTypeOptions,
ModifierPoolType,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -20,7 +28,12 @@ import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import {
HiddenAbilityRateBoosterModifier,
PokemonFormChangeItemModifier,
ShinyRateBoosterModifier,
SpeciesStatBoosterModifier,
} from "#app/modifier/modifier";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import PokemonData from "#app/system/pokemon-data";
import i18next from "i18next";
@ -51,7 +64,7 @@ const LEGENDARY_TRADE_POOLS = {
6: [Species.BUNNELBY, Species.LITLEO, Species.SCATTERBUG],
7: [Species.PIKIPEK, Species.YUNGOOS, Species.ROCKRUFF],
8: [Species.SKWOVET, Species.WOOLOO, Species.ROOKIDEE],
9: [ Species.LECHONK, Species.FIDOUGH, Species.TAROUNTULA ]
9: [Species.LECHONK, Species.FIDOUGH, Species.TAROUNTULA],
};
/** Exclude Paradox mons as they aren't considered legendary/mythical */
@ -75,7 +88,7 @@ const EXCLUDED_TRADE_SPECIES = [
Species.IRON_VALIANT,
Species.IRON_LEAVES,
Species.IRON_BOULDER,
Species.IRON_CROWN
Species.IRON_CROWN,
];
/**
@ -83,8 +96,9 @@ const EXCLUDED_TRADE_SPECIES = [
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3812 | GitHub Issue #3812}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const GlobalTradeSystemEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.GLOBAL_TRADE_SYSTEM)
export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.GLOBAL_TRADE_SYSTEM,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withAutoHideIntroVisuals(false)
@ -96,13 +110,13 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
disableAnimation: true,
x: 3,
y: 5,
yShadow: 1
}
yShadow: 1,
},
])
.withIntroDialogue([
{
text: `${namespace}:intro`,
}
},
])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
@ -128,7 +142,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const tradeOptionsMap: Map<number, EnemyPokemon[]> = getPokemonTradeOptions();
encounter.misc = {
tradeOptionsMap,
bgmKey
bgmKey,
};
return true;
@ -138,8 +152,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
@ -168,9 +181,20 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
return true;
},
onHover: () => {
const formName = tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex ? tradePokemon.species.forms[tradePokemon.formIndex].formName : null;
const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : "");
const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : "");
const formName =
tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex
? tradePokemon.species.forms[tradePokemon.formIndex].formName
: null;
const line1 = `${i18next.t("pokemonInfoContainer:ability")} ${tradePokemon.getAbility().name}${
tradePokemon.getGender() !== Gender.GENDERLESS
? ` | ${i18next.t("pokemonInfoContainer:gender")} ${getGenderSymbol(tradePokemon.getGender())}`
: ""
}`;
const line2 =
i18next.t("pokemonInfoContainer:nature") +
" " +
getNatureName(tradePokemon.getNature()) +
(formName ? ` | ${i18next.t("pokemonInfoContainer:form")} ${formName}` : "");
showEncounterText(`${line1}\n${line2}`, 0, 0, false);
},
};
@ -184,7 +208,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!;
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon;
const modifiers = tradedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
const modifiers = tradedPokemon
.getHeldItems()
.filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
// Generate a trainer name
const traderName = generateRandomTraderName();
@ -198,7 +224,18 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
// Pokeball to Ultra ball, randomly
receivedPokemonData.pokeball = randInt(4) as PokeballType;
const dataSource = new PokemonData(receivedPokemonData);
const newPlayerPokemon = globalScene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, dataSource.abilityIndex, dataSource.formIndex, dataSource.gender, dataSource.shiny, dataSource.variant, dataSource.ivs, dataSource.nature, dataSource);
const newPlayerPokemon = globalScene.addPlayerPokemon(
receivedPokemonData.species,
receivedPokemonData.level,
dataSource.abilityIndex,
dataSource.formIndex,
dataSource.gender,
dataSource.shiny,
dataSource.variant,
dataSource.ivs,
dataSource.nature,
dataSource,
);
globalScene.getPlayerParty().push(newPlayerPokemon);
await newPlayerPokemon.loadAssets();
@ -218,11 +255,10 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
@ -293,7 +329,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!;
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon;
const modifiers = tradedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
const modifiers = tradedPokemon
.getHeldItems()
.filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier));
// Generate a trainer name
const traderName = generateRandomTraderName();
@ -306,7 +344,18 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
receivedPokemonData.passive = tradedPokemon.passive;
receivedPokemonData.pokeball = randInt(4) as PokeballType;
const dataSource = new PokemonData(receivedPokemonData);
const newPlayerPokemon = globalScene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, dataSource.abilityIndex, dataSource.formIndex, dataSource.gender, dataSource.shiny, dataSource.variant, dataSource.ivs, dataSource.nature, dataSource);
const newPlayerPokemon = globalScene.addPlayerPokemon(
receivedPokemonData.species,
receivedPokemonData.level,
dataSource.abilityIndex,
dataSource.formIndex,
dataSource.gender,
dataSource.shiny,
dataSource.variant,
dataSource.ivs,
dataSource.nature,
dataSource,
);
globalScene.getPlayerParty().push(newPlayerPokemon);
await newPlayerPokemon.loadAssets();
@ -326,11 +375,10 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
@ -340,7 +388,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => {
const validItems = pokemon.getHeldItems().filter(it => {
return it.isTransferable;
});
@ -361,7 +409,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon has items to trade
const meetsReqs = pokemon.getHeldItems().filter((it) => {
const meetsReqs =
pokemon.getHeldItems().filter(it => {
return it.isTransferable;
}).length > 0;
if (!meetsReqs) {
@ -399,11 +448,17 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
let item: ModifierTypeOption | null = null;
// TMs excluded from possible rewards
while (!item || item.type.id.includes("TM_")) {
item = getPlayerModifierTypeOptions(1, party, [], { guaranteedModifierTiers: [ tier ], allowLuckUpgrades: false })[0];
item = getPlayerModifierTypeOptions(1, party, [], {
guaranteedModifierTiers: [tier],
allowLuckUpgrades: false,
})[0];
}
encounter.setDialogueToken("itemName", item.type.name);
setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeOptions: [item],
fillRemaining: false,
});
chosenPokemon.loseHeldItem(modifier, false);
await globalScene.updateModifiers(true, true);
@ -414,7 +469,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
await showEncounterText(`${namespace}:item_trade_selected`);
leaveEncounterWithoutBattle();
})
.build()
.build(),
)
.withSimpleOption(
{
@ -430,7 +485,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.build();
@ -439,7 +494,7 @@ function getPokemonTradeOptions(): Map<number, EnemyPokemon[]> {
// Starts by filtering out any current party members as valid resulting species
const alreadyUsedSpecies: PokemonSpecies[] = globalScene.getPlayerParty().map(p => p.species);
globalScene.getPlayerParty().forEach(pokemon => {
for (const pokemon of globalScene.getPlayerParty()) {
// If the party member is legendary/mythical, the only trade options available are always pulled from generation-specific legendary trade pools
if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) {
const generation = pokemon.species.generation;
@ -459,11 +514,14 @@ function getPokemonTradeOptions(): Map<number, EnemyPokemon[]> {
}
// Add trade options to map
tradeOptionsMap.set(pokemon.id, tradeOptions.map(s => {
tradeOptionsMap.set(
pokemon.id,
tradeOptions.map(s => {
return new EnemyPokemon(s, pokemon.level, TrainerSlot.NONE, false);
}));
}),
);
}
}
});
return tradeOptionsMap;
}
@ -478,8 +536,7 @@ function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?:
}
while (isNullOrUndefined(newSpecies)) {
// Get all non-legendary species that fall within the Bst range requirements
let validSpecies = allSpecies
.filter(s => {
let validSpecies = allSpecies.filter(s => {
const isLegendaryOrMythical = s.legendary || s.subLegendary || s.mythical;
const speciesBst = s.getBaseStatTotal();
const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap;
@ -508,7 +565,13 @@ function showTradeBackground() {
const tradeContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6);
tradeContainer.setName("Trade Background");
const flyByStaticBg = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0);
const flyByStaticBg = globalScene.add.rectangle(
0,
0,
globalScene.game.canvas.width / 6,
globalScene.game.canvas.height / 6,
0,
);
flyByStaticBg.setName("Black Background");
flyByStaticBg.setOrigin(0, 0);
flyByStaticBg.setVisible(false);
@ -531,7 +594,7 @@ function showTradeBackground() {
ease: "Sine.easeInOut",
onComplete: () => {
resolve();
}
},
});
});
}
@ -548,7 +611,7 @@ function hideTradeBackground() {
onComplete: () => {
globalScene.fieldUI.remove(transformationContainer, true);
resolve();
}
},
});
});
}
@ -569,8 +632,16 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
let receivedPokemonTintSprite: Phaser.GameObjects.Sprite;
const getPokemonSprite = () => {
const ret = globalScene.addPokemonSprite(tradedPokemon, tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pkmn__sub");
ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
const ret = globalScene.addPokemonSprite(
tradedPokemon,
tradeBaseBg.displayWidth / 2,
tradeBaseBg.displayHeight / 2,
"pkmn__sub",
);
ret.setPipeline(globalScene.spritePipeline, {
tone: [0.0, 0.0, 0.0, 0.0],
ignoreTimeTint: true,
});
return ret;
};
@ -594,7 +665,12 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
console.error(`Failed to play animation for ${spriteKey}`, err);
}
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()), isTerastallized: tradedPokemon.isTerastallized });
sprite.setPipeline(globalScene.spritePipeline, {
tone: [0.0, 0.0, 0.0, 0.0],
hasShadow: false,
teraColor: getTypeRgb(tradedPokemon.getTeraType()),
isTerastallized: tradedPokemon.isTerastallized,
});
sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey());
sprite.setPipelineData("shiny", tradedPokemon.shiny);
@ -615,7 +691,12 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
console.error(`Failed to play animation for ${spriteKey}`, err);
}
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()), isTerastallized: tradedPokemon.isTerastallized });
sprite.setPipeline(globalScene.spritePipeline, {
tone: [0.0, 0.0, 0.0, 0.0],
hasShadow: false,
teraColor: getTypeRgb(tradedPokemon.getTeraType()),
isTerastallized: tradedPokemon.isTerastallized,
});
sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey());
sprite.setPipelineData("shiny", receivedPokemon.shiny);
@ -630,13 +711,23 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
// Traded pokemon pokeball
const tradedPbAtlasKey = getPokeballAtlasKey(tradedPokemon.pokeball);
const tradedPokeball: Phaser.GameObjects.Sprite = globalScene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", tradedPbAtlasKey);
const tradedPokeball: Phaser.GameObjects.Sprite = globalScene.add.sprite(
tradeBaseBg.displayWidth / 2,
tradeBaseBg.displayHeight / 2,
"pb",
tradedPbAtlasKey,
);
tradedPokeball.setVisible(false);
tradeContainer.add(tradedPokeball);
// Received pokemon pokeball
const receivedPbAtlasKey = getPokeballAtlasKey(receivedPokemon.pokeball);
const receivedPokeball: Phaser.GameObjects.Sprite = globalScene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", receivedPbAtlasKey);
const receivedPokeball: Phaser.GameObjects.Sprite = globalScene.add.sprite(
tradeBaseBg.displayWidth / 2,
tradeBaseBg.displayHeight / 2,
"pb",
receivedPbAtlasKey,
);
receivedPokeball.setVisible(false);
tradeContainer.add(receivedPokeball);
@ -700,22 +791,31 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
},
onComplete: async () => {
await doPokemonTradeFlyBySequence(tradedPokemonSprite, receivedPokemonSprite);
await doTradeReceivedSequence(receivedPokemon, receivedPokemonSprite, receivedPokemonTintSprite, receivedPokeball, receivedPbAtlasKey);
await doTradeReceivedSequence(
receivedPokemon,
receivedPokemonSprite,
receivedPokemonTintSprite,
receivedPokeball,
receivedPbAtlasKey,
);
resolve();
}
},
});
}
},
});
}
},
});
}
},
});
}
},
});
});
}
function doPokemonTradeFlyBySequence(tradedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonSprite: Phaser.GameObjects.Sprite) {
function doPokemonTradeFlyBySequence(
tradedPokemonSprite: Phaser.GameObjects.Sprite,
receivedPokemonSprite: Phaser.GameObjects.Sprite,
) {
return new Promise<void>(resolve => {
const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image;
@ -728,7 +828,7 @@ function doPokemonTradeFlyBySequence(tradedPokemonSprite: Phaser.GameObjects.Spr
tradedPokemonSprite.y = 200;
tradedPokemonSprite.scale = 1;
tradedPokemonSprite.setVisible(true);
receivedPokemonSprite.x = tradeBaseBg.displayWidth * 3 / 4;
receivedPokemonSprite.x = (tradeBaseBg.displayWidth * 3) / 4;
receivedPokemonSprite.y = -200;
receivedPokemonSprite.scale = 1;
receivedPokemonSprite.setVisible(true);
@ -755,11 +855,11 @@ function doPokemonTradeFlyBySequence(tradedPokemonSprite: Phaser.GameObjects.Spr
x: tradeBaseBg.displayWidth / 4,
ease: "Cubic.easeInOut",
duration: BASE_ANIM_DURATION / 2,
delay: ANIM_DELAY
delay: ANIM_DELAY,
});
globalScene.tweens.add({
targets: tradedPokemonSprite,
x: tradeBaseBg.displayWidth * 3 / 4,
x: (tradeBaseBg.displayWidth * 3) / 4,
ease: "Cubic.easeInOut",
duration: BASE_ANIM_DURATION / 2,
delay: ANIM_DELAY,
@ -785,20 +885,26 @@ function doPokemonTradeFlyBySequence(tradedPokemonSprite: Phaser.GameObjects.Spr
duration: FADE_DELAY,
onComplete: () => {
resolve();
}
},
});
}
},
});
}
},
});
}
},
});
}
},
});
});
}
function doTradeReceivedSequence(receivedPokemon: PlayerPokemon, receivedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonTintSprite: Phaser.GameObjects.Sprite, receivedPokeballSprite: Phaser.GameObjects.Sprite, receivedPbAtlasKey: string) {
function doTradeReceivedSequence(
receivedPokemon: PlayerPokemon,
receivedPokemonSprite: Phaser.GameObjects.Sprite,
receivedPokemonTintSprite: Phaser.GameObjects.Sprite,
receivedPokeballSprite: Phaser.GameObjects.Sprite,
receivedPbAtlasKey: string,
) {
return new Promise<void>(resolve => {
const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image;
@ -851,7 +957,7 @@ function doTradeReceivedSequence(receivedPokemon: PlayerPokemon, receivedPokemon
targets: receivedPokemonSprite,
duration: 250,
ease: "Sine.easeOut",
scale: 1
scale: 1,
});
globalScene.tweens.add({
targets: receivedPokemonTintSprite,
@ -867,10 +973,10 @@ function doTradeReceivedSequence(receivedPokemon: PlayerPokemon, receivedPokemon
}
receivedPokeballSprite.destroy();
globalScene.time.delayedCall(2000, () => resolve());
}
},
});
});
}
},
});
});
}
@ -884,7 +990,7 @@ function generateRandomTraderName() {
}
// Some trainers have 2 gendered pools, some do not
const genderedPool = trainerTypePool[randInt(trainerTypePool.length)];
const trainerNameString = genderedPool instanceof Array ? genderedPool[randInt(genderedPool.length)] : genderedPool;
const trainerNameString = Array.isArray(genderedPool) ? genderedPool[randInt(genderedPool.length)] : genderedPool;
// Some names have an '&' symbol and need to be trimmed to a single name instead of a double name
const trainerNames = trainerNameString.split(" & ");
return trainerNames[randInt(trainerNames.length)];

View File

@ -29,7 +29,9 @@ const namespace = "mysteryEncounters/lostAtSea";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3793 | GitHub Issue #3793}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.LOST_AT_SEA)
export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.LOST_AT_SEA,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([
@ -57,8 +59,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
.withQuery(`${namespace}:query`)
.withOption(
// Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
@ -72,12 +73,11 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
],
})
.withOptionPhase(async () => handlePokemonGuidingYouPhase())
.build()
.build(),
)
.withOption(
//Option 2: Use a (non fainted) pokemon that can learn fly to guide you back.
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
@ -91,7 +91,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
],
})
.withOptionPhase(async () => handlePokemonGuidingYouPhase())
.build()
.build(),
)
.withSimpleOption(
// Option 3: Wander aimlessly
@ -105,7 +105,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
],
},
async () => {
const allowedPokemon = globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle());
const allowedPokemon = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle());
for (const pkm of allowedPokemon) {
const percentage = DAMAGE_PERCENTAGE / 100;
@ -116,7 +116,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
leaveEncounterWithoutBattle();
return true;
}
},
)
.withOutroDialogue([
{

View File

@ -1,5 +1,4 @@
import type {
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
initBattleWithEnemyConfig,
setEncounterRewards,
@ -29,8 +28,9 @@ const namespace = "mysteryEncounters/mysteriousChallengers";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3801 | GitHub Issue #3801}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const MysteriousChallengersEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS)
export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.MYSTERIOUS_CHALLENGERS,
)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // These are set in onInit()
@ -71,8 +71,8 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 20), 5),
PartyMemberStrength.AVERAGE,
false,
true
)
true,
),
);
const hardConfig = trainerConfigs[hardTrainerType].clone();
hardConfig.setPartyTemplates(hardTemplate);
@ -89,10 +89,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
// Brutal trainer is pulled from pool of boss trainers (gym leaders) for the biome
// They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons
const brutalTrainerType = globalScene.arena.randomTrainerType(
globalScene.currentBattle.waveIndex,
true
);
const brutalTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex, true);
const e4Template = trainerPartyTemplates.ELITE_FOUR;
const brutalConfig = trainerConfigs[brutalTrainerType].clone();
brutalConfig.title = trainerConfigs[brutalTrainerType].title;
@ -152,7 +149,10 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
// Spawn standard trainer battle with memory mushroom reward
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM ], fillRemaining: true });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM],
fillRemaining: true,
});
// Seed offsets to remove possibility of different trainers having exact same teams
let initBattlePromise: Promise<void>;
@ -160,7 +160,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
initBattlePromise = initBattleWithEnemyConfig(config);
}, globalScene.currentBattle.waveIndex * 10);
await initBattlePromise!;
}
},
)
.withSimpleOption(
{
@ -177,7 +177,10 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
// Spawn hard fight
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: true });
setEncounterRewards({
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
fillRemaining: true,
});
// Seed offsets to remove possibility of different trainers having exact same teams
let initBattlePromise: Promise<void>;
@ -185,7 +188,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
initBattlePromise = initBattleWithEnemyConfig(config);
}, globalScene.currentBattle.waveIndex * 100);
await initBattlePromise!;
}
},
)
.withSimpleOption(
{
@ -205,7 +208,10 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
// To avoid player level snowballing from picking this option
encounter.expMultiplier = 0.9;
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true });
setEncounterRewards({
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT],
fillRemaining: true,
});
// Seed offsets to remove possibility of different trainers having exact same teams
let initBattlePromise: Promise<void>;
@ -213,7 +219,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
initBattlePromise = initBattleWithEnemyConfig(config);
}, globalScene.currentBattle.waveIndex * 1000);
await initBattlePromise!;
}
},
)
.withOutroDialogue([
{

View File

@ -4,8 +4,16 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import {
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
getHighestLevelPlayerPokemon,
koPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { ModifierTier } from "#app/modifier/modifier-tier";
@ -32,8 +40,9 @@ const MASTER_REWARDS_PERCENT = 5;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3796 | GitHub Issue #3796}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const MysteriousChestEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST)
export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.MYSTERIOUS_CHEST,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(2, 6, true)
@ -57,12 +66,12 @@ export const MysteriousChestEncounter: MysteryEncounter =
yShadow: 6,
alpha: 0,
disableAnimation: true, // Re-enabled after option select
}
},
])
.withIntroDialogue([
{
text: `${namespace}:intro`,
}
},
])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
@ -80,8 +89,8 @@ export const MysteriousChestEncounter: MysteryEncounter =
species: getPokemonSpecies(Species.GIMMIGHOUL),
formIndex: 0,
isBoss: true,
moveSet: [ Moves.NASTY_PLOT, Moves.SHADOW_BALL, Moves.POWER_GEM, Moves.THIEF ]
}
moveSet: [Moves.NASTY_PLOT, Moves.SHADOW_BALL, Moves.POWER_GEM, Moves.THIEF],
},
],
};
@ -97,8 +106,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
@ -116,7 +124,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
// Determine roll first
const roll = randSeedInt(RAND_LENGTH);
encounter.misc = {
roll
roll,
};
if (roll < TRAP_PERCENT) {
@ -137,12 +145,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) {
// Choose between 2 COMMON / 2 GREAT tier items (20%)
setEncounterRewards({
guaranteedModifierTiers: [
ModifierTier.COMMON,
ModifierTier.COMMON,
ModifierTier.GREAT,
ModifierTier.GREAT,
],
guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.GREAT],
});
// Display result message then proceed to rewards
queueEncounterMessage(`${namespace}:option.1.normal`);
@ -150,24 +153,27 @@ export const MysteriousChestEncounter: MysteryEncounter =
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) {
// Choose between 3 ULTRA tier items (30%)
setEncounterRewards({
guaranteedModifierTiers: [
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA],
});
// Display result message then proceed to rewards
queueEncounterMessage(`${namespace}:option.1.good`);
leaveEncounterWithoutBattle();
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) {
// Choose between 2 ROGUE tier items (10%)
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE ]});
setEncounterRewards({
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
});
// Display result message then proceed to rewards
queueEncounterMessage(`${namespace}:option.1.great`);
leaveEncounterWithoutBattle();
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT - MASTER_REWARDS_PERCENT) {
} else if (
roll >=
RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT - MASTER_REWARDS_PERCENT
) {
// Choose 1 MASTER tier item (5%)
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.MASTER ]});
setEncounterRewards({
guaranteedModifierTiers: [ModifierTier.MASTER],
});
// Display result message then proceed to rewards
queueEncounterMessage(`${namespace}:option.1.amazing`);
leaveEncounterWithoutBattle();
@ -193,7 +199,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
}
}
})
.build()
.build(),
)
.withSimpleOption(
{
@ -209,6 +215,6 @@ export const MysteriousChestEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.build();

View File

@ -1,5 +1,12 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterExp,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -24,8 +31,9 @@ const namespace = "mysteryEncounters/partTimer";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3813 | GitHub Issue #3813}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const PartTimerEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.PART_TIMER)
export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.PART_TIMER,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([
@ -34,15 +42,15 @@ export const PartTimerEncounter: MysteryEncounter =
fileRoot: "mystery-encounters",
hasShadow: false,
y: 6,
x: 15
x: 15,
},
{
spriteKey: "worker_f",
fileRoot: "trainer",
hasShadow: true,
x: -18,
y: 4
}
y: 4,
},
])
.withAutoHideIntroVisuals(false)
.withIntroDialogue([
@ -75,16 +83,16 @@ export const PartTimerEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [
{
text: `${namespace}:option.1.selected`
}
]
text: `${namespace}:option.1.selected`,
},
],
})
.withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -95,21 +103,21 @@ export const PartTimerEncounter: MysteryEncounter =
// Calculate the "baseline" stat value (90 base stat, 16 IVs, neutral nature, same level as pokemon) to compare
// Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
// Calculation from Pokemon.calculateStats
const baselineValue = Math.floor(((2 * 90 + 16) * pokemon.level) * 0.01) + 5;
const baselineValue = Math.floor((2 * 90 + 16) * pokemon.level * 0.01) + 5;
const percentDiff = (pokemon.getStat(Stat.SPD) - baselineValue) / baselineValue;
const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4);
encounter.misc = {
moneyMultiplier
moneyMultiplier,
};
// Reduce all PP to 2 (if they started at greater than 2)
pokemon.moveset.forEach(move => {
for (const move of pokemon.moveset) {
if (move) {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
}
});
}
setEncounterExp(pokemon.id, 100);
@ -141,24 +149,28 @@ export const PartTimerEncounter: MysteryEncounter =
}
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(moneyChange, true, false);
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(
i18next.t("mysteryEncounterMessages:receive_money", {
amount: moneyChange,
}),
);
await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle();
})
.build()
.build(),
)
.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [
{
text: `${namespace}:option.2.selected`
}
]
text: `${namespace}:option.2.selected`,
},
],
})
.withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -169,24 +181,25 @@ export const PartTimerEncounter: MysteryEncounter =
// Calculate the "baseline" stat value (75 base stat, 16 IVs, neutral nature, same level as pokemon) to compare
// Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
// Calculation from Pokemon.calculateStats
const baselineHp = Math.floor(((2 * 75 + 16) * pokemon.level) * 0.01) + pokemon.level + 10;
const baselineAtkDef = Math.floor(((2 * 75 + 16) * pokemon.level) * 0.01) + 5;
const baselineHp = Math.floor((2 * 75 + 16) * pokemon.level * 0.01) + pokemon.level + 10;
const baselineAtkDef = Math.floor((2 * 75 + 16) * pokemon.level * 0.01) + 5;
const baselineValue = baselineHp + 1.5 * (baselineAtkDef * 2);
const strongestValue = pokemon.getStat(Stat.HP) + 1.5 * (pokemon.getStat(Stat.ATK) + pokemon.getStat(Stat.DEF));
const strongestValue =
pokemon.getStat(Stat.HP) + 1.5 * (pokemon.getStat(Stat.ATK) + pokemon.getStat(Stat.DEF));
const percentDiff = (strongestValue - baselineValue) / baselineValue;
const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4);
encounter.misc = {
moneyMultiplier
moneyMultiplier,
};
// Reduce all PP to 2 (if they started at greater than 2)
pokemon.moveset.forEach(move => {
for (const move of pokemon.moveset) {
if (move) {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
}
});
}
setEncounterExp(pokemon.id, 100);
@ -218,17 +231,20 @@ export const PartTimerEncounter: MysteryEncounter =
}
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(moneyChange, true, false);
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(
i18next.t("mysteryEncounterMessages:receive_money", {
amount: moneyChange,
}),
);
await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle();
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
@ -246,12 +262,12 @@ export const PartTimerEncounter: MysteryEncounter =
encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender());
// Reduce all PP to 2 (if they started at greater than 2)
selectedPokemon.moveset.forEach(move => {
for (const move of selectedPokemon.moveset) {
if (move) {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
}
});
}
setEncounterExp(selectedPokemon.id, 100);
@ -270,19 +286,23 @@ export const PartTimerEncounter: MysteryEncounter =
await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`);
const moneyChange = globalScene.getWaveMoneyAmount(2.5);
updatePlayerMoney(moneyChange, true, false);
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(
i18next.t("mysteryEncounterMessages:receive_money", {
amount: moneyChange,
}),
);
await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle();
})
.build()
.build(),
)
.withOutroDialogue([
{
speaker: `${namespace}:speaker`,
text: `${namespace}:outro`,
}
},
])
.build();

View File

@ -1,4 +1,9 @@
import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
initSubsequentOptionSelect,
leaveEncounterWithoutBattle,
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -14,7 +19,12 @@ import { NumberHolder, randSeedInt } from "#app/utils";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterCost, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import {
doPlayerFlee,
doPokemonFlee,
getRandomSpeciesByStarterCost,
trainerThrowPokeball,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -38,8 +48,9 @@ const NUM_SAFARI_ENCOUNTERS = 3;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3800 | GitHub Issue #3800}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const SafariZoneEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
export const SafariZoneEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.SAFARI_ZONE,
)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
@ -50,7 +61,7 @@ export const SafariZoneEncounter: MysteryEncounter =
fileRoot: "mystery-encounters",
hasShadow: false,
x: 4,
y: 6
y: 6,
},
])
.withIntroDialogue([
@ -66,8 +77,8 @@ export const SafariZoneEncounter: MysteryEncounter =
globalScene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString());
return true;
})
.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
@ -83,7 +94,7 @@ export const SafariZoneEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.continuousEncounter = true;
encounter.misc = {
safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS
safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS,
};
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
// Load bait/mud assets
@ -96,10 +107,13 @@ export const SafariZoneEncounter: MysteryEncounter =
globalScene.currentBattle.enemyParty = [];
await transitionMysteryEncounterIntroVisuals();
await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, hideDescription: true });
initSubsequentOptionSelect({
overrideOptions: safariZoneGameOptions,
hideDescription: true,
});
return true;
})
.build()
.build(),
)
.withSimpleOption(
{
@ -115,7 +129,7 @@ export const SafariZoneEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.build();
@ -135,15 +149,14 @@ export const SafariZoneEncounter: MysteryEncounter =
* Flee chance = fleeRate / 255
*/
const safariZoneGameOptions: MysteryEncounterOption[] = [
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:safari.1.label`,
buttonTooltip: `${namespace}:safari.1.tooltip`,
selected: [
{
text: `${namespace}:safari.1.selected`,
}
},
],
})
.withOptionPhase(async () => {
@ -157,7 +170,11 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
// Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true });
initSubsequentOptionSelect({
overrideOptions: safariZoneGameOptions,
startingCursorIndex: 0,
hideDescription: true,
});
} else {
// End safari mode
encounter.continuousEncounter = false;
@ -170,8 +187,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
return true;
})
.build(),
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:safari.2.label`,
buttonTooltip: `${namespace}:safari.2.tooltip`,
@ -200,8 +216,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
return true;
})
.build(),
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:safari.3.label`,
buttonTooltip: `${namespace}:safari.3.tooltip`,
@ -229,8 +244,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
return true;
})
.build(),
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:safari.4.label`,
buttonTooltip: `${namespace}:safari.4.tooltip`,
@ -243,7 +257,11 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
// Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true });
initSubsequentOptionSelect({
overrideOptions: safariZoneGameOptions,
startingCursorIndex: 3,
hideDescription: true,
});
} else {
// End safari mode
encounter.continuousEncounter = false;
@ -251,7 +269,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
}
return true;
})
.build()
.build(),
];
async function summonSafariPokemon() {
@ -262,9 +280,10 @@ async function summonSafariPokemon() {
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
let enemySpecies;
let pokemon;
globalScene.executeWithSeedOffset(() => {
let enemySpecies: PokemonSpecies;
let pokemon: any;
globalScene.executeWithSeedOffset(
() => {
enemySpecies = getSafariSpeciesSpawn();
const level = globalScene.currentBattle.getLevelForWave();
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, globalScene.gameMode));
@ -293,7 +312,9 @@ async function summonSafariPokemon() {
pokemon.calculateStats();
globalScene.currentBattle.enemyParty.unshift(pokemon);
}, globalScene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining);
},
globalScene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining,
);
globalScene.gameData.setPokemonSeen(pokemon, true);
await pokemon.loadAssets();
@ -324,7 +345,8 @@ function throwPokeball(pokemon: EnemyPokemon): Promise<boolean> {
// Catch stage ranges from -6 to +6 (like stat boost stages)
const safariCatchStage = globalScene.currentBattle.mysteryEncounter!.misc.catchStage;
// Catch modifier ranges from 2/8 (-6 stage) to 8/2 (+6)
const safariModifier = (2 + Math.min(Math.max(safariCatchStage, 0), 6)) / (2 - Math.max(Math.min(safariCatchStage, 0), -6));
const safariModifier =
(2 + Math.min(Math.max(safariCatchStage, 0), 6)) / (2 - Math.max(Math.min(safariCatchStage, 0), -6));
// Catch rate same as safari ball
const pokeballMultiplier = 1.5;
const catchRate = Math.round(baseCatchRate * pokeballMultiplier * safariModifier);
@ -341,7 +363,9 @@ async function throwBait(pokemon: EnemyPokemon): Promise<boolean> {
globalScene.field.add(bait);
return new Promise(resolve => {
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
globalScene.trainer.setTexture(
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`,
);
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
globalScene.playSound("se/pb_throw");
@ -350,7 +374,9 @@ async function throwBait(pokemon: EnemyPokemon): Promise<boolean> {
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
globalScene.trainer.setFrame("3");
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
globalScene.trainer.setTexture(
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`,
);
});
});
@ -361,7 +387,6 @@ async function throwBait(pokemon: EnemyPokemon): Promise<boolean> {
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
duration: 500,
onComplete: () => {
let index = 1;
globalScene.time.delayedCall(768, () => {
globalScene.tweens.add({
@ -389,10 +414,10 @@ async function throwBait(pokemon: EnemyPokemon): Promise<boolean> {
bait.destroy();
resolve(true);
});
}
},
});
});
}
},
});
});
});
@ -407,7 +432,9 @@ async function throwMud(pokemon: EnemyPokemon): Promise<boolean> {
globalScene.field.add(mud);
return new Promise(resolve => {
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
globalScene.trainer.setTexture(
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`,
);
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
globalScene.playSound("se/pb_throw");
@ -416,7 +443,9 @@ async function throwMud(pokemon: EnemyPokemon): Promise<boolean> {
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
globalScene.trainer.setFrame("3");
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
globalScene.trainer.setTexture(
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`,
);
});
});
@ -461,11 +490,11 @@ async function throwMud(pokemon: EnemyPokemon): Promise<boolean> {
},
onComplete: () => {
resolve(true);
}
},
});
}
},
});
}
},
});
});
});
@ -474,7 +503,7 @@ async function throwMud(pokemon: EnemyPokemon): Promise<boolean> {
function isPokemonFlee(pokemon: EnemyPokemon, fleeStage: number): boolean {
const speciesCatchRate = pokemon.species.catchRate;
const fleeModifier = (2 + Math.min(Math.max(fleeStage, 0), 6)) / (2 - Math.max(Math.min(fleeStage, 0), -6));
const fleeRate = (255 * 255 - speciesCatchRate * speciesCatchRate) / 255 / 2 * fleeModifier;
const fleeRate = ((255 * 255 - speciesCatchRate * speciesCatchRate) / 255 / 2) * fleeModifier;
console.log("Flee rate: " + fleeRate);
const roll = randSeedInt(256);
console.log("Roll: " + roll);
@ -519,7 +548,11 @@ async function doEndTurn(cursorIndex: number) {
// Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
initSubsequentOptionSelect({
overrideOptions: safariZoneGameOptions,
startingCursorIndex: cursorIndex,
hideDescription: true,
});
} else {
// End safari mode
encounter.continuousEncounter = false;
@ -527,7 +560,11 @@ async function doEndTurn(cursorIndex: number) {
}
} else {
globalScene.queueMessage(getEncounterText(`${namespace}:safari.watching`) ?? "", 0, null, 1000);
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
initSubsequentOptionSelect({
overrideOptions: safariZoneGameOptions,
startingCursorIndex: cursorIndex,
hideDescription: true,
});
}
}
@ -535,5 +572,7 @@ async function doEndTurn(cursorIndex: number) {
* @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
*/
export function getSafariSpeciesSpawn(): PokemonSpecies {
return getPokemonSpecies(getRandomSpeciesByStarterCost([ 0, 5 ], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false));
return getPokemonSpecies(
getRandomSpeciesByStarterCost([0, 5], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false),
);
}

View File

@ -1,4 +1,10 @@
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierType,
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterExp,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type";
@ -11,7 +17,11 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon, isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import {
applyDamageToPokemon,
applyModifierTypeToPlayerPokemon,
isPokemonValidForEncounterOptionSelection,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type { Nature } from "#enums/nature";
@ -30,8 +40,9 @@ const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 5;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3798 | GitHub Issue #3798}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ShadyVitaminDealerEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER)
export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.SHADY_VITAMIN_DEALER,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)) // Must have the money for at least the cheap deal
@ -44,7 +55,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
repeat: true,
x: 12,
y: -5,
yShadow: -5
yShadow: -5,
},
{
spriteKey: "shady_vitamin_dealer",
@ -52,7 +63,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
hasShadow: true,
x: -12,
y: 3,
yShadow: 3
yShadow: 3,
},
])
.withIntroDialogue([
@ -69,8 +80,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
@ -103,7 +113,11 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon meets primary pokemon reqs, it can be selected
if (!pokemon.isAllowedInChallenge()) {
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
return (
i18next.t("partyUiHandler:cantBeUsed", {
pokemonName: pokemon.getNameToRender(),
}) ?? null
);
}
if (!encounter.pokemonMeetsPrimaryRequirements(pokemon)) {
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
@ -146,11 +160,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
setEncounterExp([chosenPokemon.id], 100);
await chosenPokemon.updateInfo();
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
@ -208,7 +221,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
await chosenPokemon.updateInfo();
})
.build()
.build(),
)
.withSimpleOption(
{
@ -217,14 +230,14 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
selected: [
{
text: `${namespace}:option.3.selected`,
speaker: `${namespace}:speaker`
}
]
speaker: `${namespace}:speaker`,
},
],
},
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.build();

View File

@ -10,7 +10,14 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
import {
generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
loadCustomMovesForEncounter,
setEncounterExp,
setEncounterRewards,
} from "../utils/encounter-phase-utils";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
@ -31,8 +38,9 @@ const namespace = "mysteryEncounters/slumberingSnorlax";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3815 | GitHub Issue #3815}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const SlumberingSnorlaxEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX)
export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.SLUMBERING_SNORLAX,
)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withCatchAllowed(true)
@ -69,15 +77,15 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
stackCount: 2
stackCount: 2,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
stackCount: 2
stackCount: 2,
},
],
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep
aiType: AiType.SMART, // Required to ensure Snorlax uses Sleep Talk while it is asleep
};
const config: EnemyPartyConfig = {
levelAdditiveModifier: 0.5,
@ -109,22 +117,26 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
async () => {
// Pick battle
const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: true });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
fillRemaining: true,
});
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.SNORE),
ignorePp: true
ignorePp: true,
},
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.SNORE),
ignorePp: true
});
ignorePp: true,
},
);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
}
},
)
.withSimpleOption(
{
@ -142,11 +154,10 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
globalScene.unshiftPhase(new PartyHealPhase(true));
queueEncounterMessage(`${namespace}:option.2.rest_result`);
leaveEncounterWithoutBattle();
}
},
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true))
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
@ -154,18 +165,21 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
selected: [
{
text: `${namespace}:option.3.selected`
}
]
text: `${namespace}:option.3.selected`,
},
],
})
.withOptionPhase(async () => {
// Steal the Snorlax's Leftovers
const instance = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
fillRemaining: false,
});
// Snorlax exp to Pokemon that did the stealing
setEncounterExp(instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp);
leaveEncounterWithoutBattle();
})
.build()
.build(),
)
.build();

View File

@ -1,5 +1,12 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierTypeOption, initBattleWithEnemyConfig, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierTypeOption,
initBattleWithEnemyConfig,
setEncounterExp,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
@ -15,7 +22,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Biome } from "#enums/biome";
import { getBiomeKey } from "#app/field/arena";
import { Type } from "#enums/type";
import { PokemonType } from "#enums/pokemon-type";
import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type";
import { TrainerSlot } from "#app/data/trainer-config";
import { BattlerTagType } from "#enums/battler-tag-type";
@ -23,22 +30,26 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import {
getEncounterPokemonLevelForWave,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/teleportingHijinks";
const MONEY_COST_MULTIPLIER = 1.75;
const BIOME_CANDIDATES = [Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND, Biome.WASTELAND, Biome.DOJO];
const MACHINE_INTERFACING_TYPES = [ Type.ELECTRIC, Type.STEEL ];
const MACHINE_INTERFACING_TYPES = [PokemonType.ELECTRIC, PokemonType.STEEL];
/**
* Teleporting Hijinks encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3817 | GitHub Issue #3817}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TeleportingHijinksEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TELEPORTING_HIJINKS)
export const TeleportingHijinksEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.TELEPORTING_HIJINKS,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave
@ -53,13 +64,13 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
hasShadow: true,
x: 4,
y: 4,
yShadow: 1
}
yShadow: 1,
},
])
.withIntroDialogue([
{
text: `${namespace}:intro`,
}
},
])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
@ -70,14 +81,13 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
const price = globalScene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER);
encounter.setDialogueToken("price", price.toString());
encounter.misc = {
price
price,
};
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
@ -85,7 +95,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
selected: [
{
text: `${namespace}:option.1.selected`,
}
},
],
})
.withPreOptionPhase(async () => {
@ -97,11 +107,10 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
setEncounterRewards({ fillRemaining: true });
await initBattleWithEnemyConfig(config);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPokemonTypeRequirement(MACHINE_INTERFACING_TYPES, true, 1) // Must have Steel or Electric type
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
@ -110,7 +119,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
selected: [
{
text: `${namespace}:option.2.selected`,
}
},
],
})
.withOptionPhase(async () => {
@ -119,7 +128,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
setEncounterExp(globalScene.currentBattle.mysteryEncounter!.selectedOption!.primaryPokemon!.id, 100);
await initBattleWithEnemyConfig(config);
})
.build()
.build(),
)
.withSimpleOption(
{
@ -137,24 +146,35 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
// Init enemy
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true);
const bossSpecies = globalScene.arena.randomSpecies(
globalScene.currentBattle.waveIndex,
level,
0,
getPartyLuckValue(globalScene.getPlayerParty()),
true,
);
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = {
pokemonConfigs: [{
pokemonConfigs: [
{
level: level,
species: bossSpecies,
dataSource: new PokemonData(bossPokemon),
isBoss: true,
}],
},
],
};
const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.STEEL ])!;
const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.ELECTRIC ])!;
setEncounterRewards({ guaranteedModifierTypeOptions: [ magnet, metalCoat ], fillRemaining: true });
const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.STEEL])!;
const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.ELECTRIC])!;
setEncounterRewards({
guaranteedModifierTypeOptions: [magnet, metalCoat],
fillRemaining: true,
});
await transitionMysteryEncounterIntroVisuals(true, true);
await initBattleWithEnemyConfig(config);
}
},
)
.build();
@ -174,17 +194,25 @@ async function doBiomeTransitionDialogueAndBattleInit() {
// Init enemy
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true);
const bossSpecies = globalScene.arena.randomSpecies(
globalScene.currentBattle.waveIndex,
level,
0,
getPartyLuckValue(globalScene.getPlayerParty()),
true,
);
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
[ Stat.DEF, Stat.SPDEF, Stat.SPD ] :
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
globalScene.currentBattle.waveIndex < 50
? [Stat.DEF, Stat.SPDEF, Stat.SPD]
: [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
const config: EnemyPartyConfig = {
pokemonConfigs: [{
pokemonConfigs: [
{
level: level,
species: bossSpecies,
dataSource: new PokemonData(bossPokemon),
@ -193,8 +221,9 @@ async function doBiomeTransitionDialogueAndBattleInit() {
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:boss_enraged`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
}
}],
},
},
],
};
return config;
@ -222,7 +251,7 @@ async function animateBiomeChange(nextBiome: Biome) {
targets: [globalScene.arenaPlayer, globalScene.arenaBgTransition, globalScene.arenaPlayerTransition],
duration: 1000,
ease: "Sine.easeInOut",
alpha: (target: any) => target === globalScene.arenaPlayer ? 0 : 1,
alpha: (target: any) => (target === globalScene.arenaPlayer ? 0 : 1),
onComplete: () => {
globalScene.arenaBg.setTexture(bgTexture);
globalScene.arenaPlayer.setBiome(nextBiome);
@ -242,9 +271,9 @@ async function animateBiomeChange(nextBiome: Biome) {
targets: globalScene.arenaEnemy,
x: "-=300",
});
}
},
});
}
},
});
});
}

View File

@ -1,5 +1,9 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
handleMysteryEncounterBattleFailed,
initBattleWithEnemyConfig,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
@ -24,7 +28,7 @@ import { EggTier } from "#enums/egg-type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { modifierTypes } from "#app/modifier/modifier-type";
import { Type } from "#enums/type";
import { PokemonType } from "#enums/pokemon-type";
import { getPokeballTintColor } from "#app/data/pokeball";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
@ -51,28 +55,64 @@ class BreederSpeciesEvolution {
const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
[Species.MUNCHLAX, new BreederSpeciesEvolution(Species.SNORLAX, SECOND_STAGE_EVOLUTION_WAVE)],
[ Species.HAPPINY, new BreederSpeciesEvolution(Species.CHANSEY, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.BLISSEY, FINAL_STAGE_EVOLUTION_WAVE) ],
[ Species.MAGBY, new BreederSpeciesEvolution(Species.MAGMAR, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.MAGMORTAR, FINAL_STAGE_EVOLUTION_WAVE) ],
[ Species.ELEKID, new BreederSpeciesEvolution(Species.ELECTABUZZ, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ELECTIVIRE, FINAL_STAGE_EVOLUTION_WAVE) ],
[
Species.HAPPINY,
new BreederSpeciesEvolution(Species.CHANSEY, FIRST_STAGE_EVOLUTION_WAVE),
new BreederSpeciesEvolution(Species.BLISSEY, FINAL_STAGE_EVOLUTION_WAVE),
],
[
Species.MAGBY,
new BreederSpeciesEvolution(Species.MAGMAR, FIRST_STAGE_EVOLUTION_WAVE),
new BreederSpeciesEvolution(Species.MAGMORTAR, FINAL_STAGE_EVOLUTION_WAVE),
],
[
Species.ELEKID,
new BreederSpeciesEvolution(Species.ELECTABUZZ, FIRST_STAGE_EVOLUTION_WAVE),
new BreederSpeciesEvolution(Species.ELECTIVIRE, FINAL_STAGE_EVOLUTION_WAVE),
],
[Species.RIOLU, new BreederSpeciesEvolution(Species.LUCARIO, SECOND_STAGE_EVOLUTION_WAVE)],
[ Species.BUDEW, new BreederSpeciesEvolution(Species.ROSELIA, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ROSERADE, FINAL_STAGE_EVOLUTION_WAVE) ],
[
Species.BUDEW,
new BreederSpeciesEvolution(Species.ROSELIA, FIRST_STAGE_EVOLUTION_WAVE),
new BreederSpeciesEvolution(Species.ROSERADE, FINAL_STAGE_EVOLUTION_WAVE),
],
[Species.TOXEL, new BreederSpeciesEvolution(Species.TOXTRICITY, SECOND_STAGE_EVOLUTION_WAVE)],
[ Species.MIME_JR, new BreederSpeciesEvolution(Species.GALAR_MR_MIME, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.MR_RIME, FINAL_STAGE_EVOLUTION_WAVE) ]
[
Species.MIME_JR,
new BreederSpeciesEvolution(Species.GALAR_MR_MIME, FIRST_STAGE_EVOLUTION_WAVE),
new BreederSpeciesEvolution(Species.MR_RIME, FINAL_STAGE_EVOLUTION_WAVE),
],
];
const POOL_2_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ],
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ],
[
Species.PICHU,
new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE),
new BreederSpeciesEvolution(Species.RAICHU, FINAL_STAGE_EVOLUTION_WAVE),
],
[
Species.PICHU,
new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE),
new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE),
],
[Species.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE)],
[ Species.IGGLYBUFF, new BreederSpeciesEvolution(Species.JIGGLYPUFF, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.WIGGLYTUFF, FINAL_STAGE_EVOLUTION_WAVE) ],
[ Species.AZURILL, new BreederSpeciesEvolution(Species.MARILL, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.AZUMARILL, FINAL_STAGE_EVOLUTION_WAVE) ],
[
Species.IGGLYBUFF,
new BreederSpeciesEvolution(Species.JIGGLYPUFF, FIRST_STAGE_EVOLUTION_WAVE),
new BreederSpeciesEvolution(Species.WIGGLYTUFF, FINAL_STAGE_EVOLUTION_WAVE),
],
[
Species.AZURILL,
new BreederSpeciesEvolution(Species.MARILL, FIRST_STAGE_EVOLUTION_WAVE),
new BreederSpeciesEvolution(Species.AZUMARILL, FINAL_STAGE_EVOLUTION_WAVE),
],
[Species.WYNAUT, new BreederSpeciesEvolution(Species.WOBBUFFET, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.CHINGLING, new BreederSpeciesEvolution(Species.CHIMECHO, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.BONSLY, new BreederSpeciesEvolution(Species.SUDOWOODO, SECOND_STAGE_EVOLUTION_WAVE)],
[ Species.MANTYKE, new BreederSpeciesEvolution(Species.MANTINE, SECOND_STAGE_EVOLUTION_WAVE) ]
[Species.MANTYKE, new BreederSpeciesEvolution(Species.MANTINE, SECOND_STAGE_EVOLUTION_WAVE)],
];
/**
@ -80,8 +120,9 @@ const POOL_2_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3818 | GitHub Issue #3818}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TheExpertPokemonBreederEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER)
export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER,
)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(4, 6, true) // Must have at least 4 legal pokemon in party
@ -101,11 +142,14 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
// Calculates what trainers are available for battle in the encounter
// If player is in space biome, uses special "Space" version of the trainer
encounter.enemyPartyConfigs = [
getPartyConfig()
];
encounter.enemyPartyConfigs = [getPartyConfig()];
const cleffaSpecies = waveIndex < FIRST_STAGE_EVOLUTION_WAVE ? Species.CLEFFA : waveIndex < FINAL_STAGE_EVOLUTION_WAVE ? Species.CLEFAIRY : Species.CLEFABLE;
const cleffaSpecies =
waveIndex < FIRST_STAGE_EVOLUTION_WAVE
? Species.CLEFFA
: waveIndex < FINAL_STAGE_EVOLUTION_WAVE
? Species.CLEFAIRY
: Species.CLEFABLE;
encounter.spriteConfigs = [
{
spriteKey: cleffaSpecies.toString(),
@ -114,7 +158,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
repeat: true,
x: 14,
y: -2,
yShadow: -2
yShadow: -2,
},
{
spriteKey: "expert_pokemon_breeder",
@ -122,15 +166,13 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
hasShadow: true,
x: -14,
y: 4,
yShadow: 2
yShadow: 2,
},
];
// Determine the 3 pokemon the player can battle with
let partyCopy = globalScene.getPlayerParty().slice(0);
partyCopy = partyCopy
.filter(p => p.isAllowedInBattle())
.sort((a, b) => a.friendship - b.friendship);
partyCopy = partyCopy.filter(p => p.isAllowedInBattle()).sort((a, b) => a.friendship - b.friendship);
const pokemon1 = partyCopy[0];
const pokemon2 = partyCopy[1];
@ -143,13 +185,23 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
const [pokemon1CommonEggs, pokemon1RareEggs] = calculateEggRewardsForPokemon(pokemon1);
let pokemon1Tooltip = getEncounterText(`${namespace}:option.1.tooltip_base`)!;
if (pokemon1RareEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
const eggsText = i18next.t(`${namespace}:numEggs`, {
count: pokemon1RareEggs,
rarity: i18next.t("egg:greatTier"),
});
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon1RareEggs", eggsText);
}
if (pokemon1CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1CommonEggs, rarity: i18next.t("egg:defaultTier") });
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
const eggsText = i18next.t(`${namespace}:numEggs`, {
count: pokemon1CommonEggs,
rarity: i18next.t("egg:defaultTier"),
});
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon1CommonEggs", eggsText);
}
encounter.options[0].dialogue!.buttonTooltip = pokemon1Tooltip;
@ -158,13 +210,23 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
const [pokemon2CommonEggs, pokemon2RareEggs] = calculateEggRewardsForPokemon(pokemon2);
let pokemon2Tooltip = getEncounterText(`${namespace}:option.2.tooltip_base`)!;
if (pokemon2RareEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
const eggsText = i18next.t(`${namespace}:numEggs`, {
count: pokemon2RareEggs,
rarity: i18next.t("egg:greatTier"),
});
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon2RareEggs", eggsText);
}
if (pokemon2CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") });
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
const eggsText = i18next.t(`${namespace}:numEggs`, {
count: pokemon2CommonEggs,
rarity: i18next.t("egg:defaultTier"),
});
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon2CommonEggs", eggsText);
}
encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip;
@ -173,13 +235,23 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
const [pokemon3CommonEggs, pokemon3RareEggs] = calculateEggRewardsForPokemon(pokemon3);
let pokemon3Tooltip = getEncounterText(`${namespace}:option.3.tooltip_base`)!;
if (pokemon3RareEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
const eggsText = i18next.t(`${namespace}:numEggs`, {
count: pokemon3RareEggs,
rarity: i18next.t("egg:greatTier"),
});
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon3RareEggs", eggsText);
}
if (pokemon3CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3CommonEggs, rarity: i18next.t("egg:defaultTier") });
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
const eggsText = i18next.t(`${namespace}:numEggs`, {
count: pokemon3CommonEggs,
rarity: i18next.t("egg:defaultTier"),
});
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon3CommonEggs", eggsText);
}
encounter.options[2].dialogue!.buttonTooltip = pokemon3Tooltip;
@ -193,7 +265,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
pokemon2RareEggs,
pokemon3,
pokemon3CommonEggs,
pokemon3RareEggs
pokemon3RareEggs,
};
return true;
@ -203,8 +275,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
selected: [
@ -224,9 +295,13 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs);
setEncounterRewards(
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
{
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
fillRemaining: true,
},
eggOptions,
() => doPostEncounterCleanup());
() => doPostEncounterCleanup(),
);
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon1);
@ -240,23 +315,26 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
];
if (encounter.dialogueTokens.hasOwnProperty("pokemon1CommonEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1CommonEggs"] }),
text: i18next.t(`${namespace}:gained_eggs`, {
numEggs: encounter.dialogueTokens["pokemon1CommonEggs"],
}),
});
}
if (encounter.dialogueTokens.hasOwnProperty("pokemon1RareEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1RareEggs"] }),
text: i18next.t(`${namespace}:gained_eggs`, {
numEggs: encounter.dialogueTokens["pokemon1RareEggs"],
}),
});
}
encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(config);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
selected: [
@ -276,9 +354,13 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs);
setEncounterRewards(
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
{
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
fillRemaining: true,
},
eggOptions,
() => doPostEncounterCleanup());
() => doPostEncounterCleanup(),
);
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon2);
@ -292,23 +374,26 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
];
if (encounter.dialogueTokens.hasOwnProperty("pokemon2CommonEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2CommonEggs"] }),
text: i18next.t(`${namespace}:gained_eggs`, {
numEggs: encounter.dialogueTokens["pokemon2CommonEggs"],
}),
});
}
if (encounter.dialogueTokens.hasOwnProperty("pokemon2RareEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2RareEggs"] }),
text: i18next.t(`${namespace}:gained_eggs`, {
numEggs: encounter.dialogueTokens["pokemon2RareEggs"],
}),
});
}
encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(config);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
selected: [
@ -328,9 +413,13 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs);
setEncounterRewards(
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
{
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
fillRemaining: true,
},
eggOptions,
() => doPostEncounterCleanup());
() => doPostEncounterCleanup(),
);
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon3);
@ -344,19 +433,23 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
];
if (encounter.dialogueTokens.hasOwnProperty("pokemon3CommonEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3CommonEggs"] }),
text: i18next.t(`${namespace}:gained_eggs`, {
numEggs: encounter.dialogueTokens["pokemon3CommonEggs"],
}),
});
}
if (encounter.dialogueTokens.hasOwnProperty("pokemon3RareEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3RareEggs"] }),
text: i18next.t(`${namespace}:gained_eggs`, {
numEggs: encounter.dialogueTokens["pokemon3RareEggs"],
}),
});
}
encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(config);
})
.build()
.build(),
)
.withOutroDialogue([
{
@ -373,12 +466,19 @@ function getPartyConfig(): EnemyPartyConfig {
breederConfig.name = i18next.t(trainerNameKey);
// First mon is *always* this special cleffa
const cleffaSpecies = waveIndex < FIRST_STAGE_EVOLUTION_WAVE ? Species.CLEFFA : waveIndex < FINAL_STAGE_EVOLUTION_WAVE ? Species.CLEFAIRY : Species.CLEFABLE;
const cleffaSpecies =
waveIndex < FIRST_STAGE_EVOLUTION_WAVE
? Species.CLEFFA
: waveIndex < FINAL_STAGE_EVOLUTION_WAVE
? Species.CLEFAIRY
: Species.CLEFABLE;
const baseConfig: EnemyPartyConfig = {
trainerType: TrainerType.EXPERT_POKEMON_BREEDER,
pokemonConfigs: [
{
nickname: i18next.t(`${namespace}:cleffa_1_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }),
nickname: i18next.t(`${namespace}:cleffa_1_nickname`, {
speciesName: getPokemonSpecies(cleffaSpecies).getName(),
}),
species: getPokemonSpecies(cleffaSpecies),
isBoss: false,
abilityIndex: 1, // Magic Guard
@ -386,15 +486,18 @@ function getPartyConfig(): EnemyPartyConfig {
nature: Nature.ADAMANT,
moveSet: [Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH],
ivs: [31, 31, 31, 31, 31, 31],
tera: Type.STEEL,
}
]
tera: PokemonType.STEEL,
},
],
};
if (globalScene.arena.biomeType === Biome.SPACE) {
// All 3 members always Cleffa line, but different configs
baseConfig.pokemonConfigs!.push({
nickname: i18next.t(`${namespace}:cleffa_2_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }),
baseConfig.pokemonConfigs!.push(
{
nickname: i18next.t(`${namespace}:cleffa_2_nickname`, {
speciesName: getPokemonSpecies(cleffaSpecies).getName(),
}),
species: getPokemonSpecies(cleffaSpecies),
isBoss: false,
abilityIndex: 1, // Magic Guard
@ -402,10 +505,12 @@ function getPartyConfig(): EnemyPartyConfig {
variant: 1,
nature: Nature.MODEST,
moveSet: [Moves.MOONBLAST, Moves.MYSTICAL_FIRE, Moves.ICE_BEAM, Moves.THUNDERBOLT],
ivs: [ 31, 31, 31, 31, 31, 31 ]
ivs: [31, 31, 31, 31, 31, 31],
},
{
nickname: i18next.t(`${namespace}:cleffa_3_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }),
nickname: i18next.t(`${namespace}:cleffa_3_nickname`, {
speciesName: getPokemonSpecies(cleffaSpecies).getName(),
}),
species: getPokemonSpecies(cleffaSpecies),
isBoss: false,
abilityIndex: 2, // Friend Guard / Unaware
@ -413,24 +518,27 @@ function getPartyConfig(): EnemyPartyConfig {
variant: 2,
nature: Nature.BOLD,
moveSet: [Moves.TRI_ATTACK, Moves.STORED_POWER, Moves.TAKE_HEART, Moves.MOONLIGHT],
ivs: [ 31, 31, 31, 31, 31, 31 ]
});
ivs: [31, 31, 31, 31, 31, 31],
},
);
} else {
// Second member from pool 1
const pool1Species = getSpeciesFromPool(POOL_1_POKEMON, waveIndex);
// Third member from pool 2
const pool2Species = getSpeciesFromPool(POOL_2_POKEMON, waveIndex);
baseConfig.pokemonConfigs!.push({
baseConfig.pokemonConfigs!.push(
{
species: getPokemonSpecies(pool1Species),
isBoss: false,
ivs: [ 31, 31, 31, 31, 31, 31 ]
ivs: [31, 31, 31, 31, 31, 31],
},
{
species: getPokemonSpecies(pool2Species),
isBoss: false,
ivs: [ 31, 31, 31, 31, 31, 31 ]
});
ivs: [31, 31, 31, 31, 31, 31],
},
);
}
return baseConfig;
@ -484,7 +592,7 @@ function getEggOptions(commonEggs: number, rareEggs: number) {
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription,
tier: EggTier.COMMON
tier: EggTier.COMMON,
});
}
}
@ -494,7 +602,7 @@ function getEggOptions(commonEggs: number, rareEggs: number) {
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription,
tier: EggTier.RARE
tier: EggTier.RARE,
});
}
}
@ -508,11 +616,8 @@ function removePokemonFromPartyAndStoreHeldItems(encounter: MysteryEncounter, ch
party[chosenIndex] = party[0];
party[0] = chosenPokemon;
encounter.misc.originalParty = globalScene.getPlayerParty().slice(1);
encounter.misc.originalPartyHeldItems = encounter.misc.originalParty
.map(p => p.getHeldItems());
globalScene["party"] = [
chosenPokemon
];
encounter.misc.originalPartyHeldItems = encounter.misc.originalParty.map(p => p.getHeldItems());
globalScene["party"] = [chosenPokemon];
}
function restorePartyAndHeldItems() {
@ -522,11 +627,11 @@ function restorePartyAndHeldItems() {
// Restore held items
const originalHeldItems = encounter.misc.originalPartyHeldItems;
originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => {
pokemonHeldItemsList.forEach(heldItem => {
for (const pokemonHeldItemsList of originalHeldItems) {
for (const heldItem of pokemonHeldItemsList) {
globalScene.addModifier(heldItem, true, false, false, true);
});
});
}
}
globalScene.updateModifiers(true);
}
@ -571,7 +676,7 @@ function onGameOver() {
scale: 0.5,
onComplete: () => {
pokemon.leaveField(true, true, true);
}
},
});
}
@ -593,11 +698,10 @@ function onGameOver() {
y: "+=16",
alpha: 1,
ease: "Sine.easeInOut",
duration: 750
duration: 750,
});
});
handleMysteryEncounterBattleFailed(true);
return false;

View File

@ -1,11 +1,19 @@
import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
leaveEncounterWithoutBattle,
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { catchPokemon, getRandomSpeciesByStarterCost, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import {
catchPokemon,
getRandomSpeciesByStarterCost,
getSpriteKeysFromPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
@ -35,8 +43,9 @@ const SHINY_MAGIKARP_WEIGHT = 100;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3799 | GitHub Issue #3799}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ThePokemonSalesmanEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN)
export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.THE_POKEMON_SALESMAN,
)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
@ -45,8 +54,8 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
{
spriteKey: "pokemon_salesman",
fileRoot: "mystery-encounters",
hasShadow: true
}
hasShadow: true,
},
])
.withIntroDialogue([
{
@ -74,7 +83,11 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
}
let pokemon: PlayerPokemon;
if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) {
if (
randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 ||
isNullOrUndefined(species.abilityHidden) ||
species.abilityHidden === Abilities.NONE
) {
// If no HA mon found or you roll 1%, give shiny Magikarp with random variant
species = getPokemonSpecies(Species.MAGIKARP);
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex, undefined, true);
@ -91,7 +104,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
repeat: true,
isPokemon: true,
isShiny: pokemon.shiny,
variant: pokemon.variant
variant: pokemon.variant,
});
const starterTier = speciesStarterCosts[species.speciesId];
@ -109,7 +122,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
encounter.setDialogueToken("price", price.toString());
encounter.misc = {
price: price,
pokemon: pokemon
pokemon: pokemon,
};
pokemon.calculateStats();
@ -117,8 +130,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withHasDexProgress(true)
.withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
.withDialogue({
@ -127,7 +139,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
selected: [
{
text: `${namespace}:option.1.selected_message`,
}
},
],
})
.withOptionPhase(async () => {
@ -149,7 +161,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.withSimpleOption(
{
@ -165,7 +177,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.build();
@ -173,5 +185,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
* @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
*/
export function getSalesmanSpeciesOffer(): PokemonSpecies {
return getPokemonSpecies(getRandomSpeciesByStarterCost([ 0, 5 ], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false));
return getPokemonSpecies(
getRandomSpeciesByStarterCost([0, 5], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false),
);
}

View File

@ -1,5 +1,12 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
initBattleWithEnemyConfig,
loadCustomMovesForEncounter,
leaveEncounterWithoutBattle,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
generateModifierType,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -35,8 +42,9 @@ const BST_INCREASE_VALUE = 10;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3803 | GitHub Issue #3803}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TheStrongStuffEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.THE_STRONG_STUFF,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
@ -53,7 +61,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
scale: 1.25,
x: -15,
y: 3,
disableAnimation: true
disableAnimation: true,
},
{
spriteKey: Species.SHUCKLE.toString(),
@ -63,7 +71,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
scale: 1.25,
x: 20,
y: 10,
yShadow: 7
yShadow: 7,
},
]) // Set in onInit()
.withIntroDialogue([
@ -89,28 +97,30 @@ export const TheStrongStuffEncounter: MysteryEncounter =
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2
}
stackCount: 2,
},
],
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.2.stat_boost`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.DEF, Stat.SPDEF ], 2));
}
}
globalScene.unshiftPhase(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.DEF, Stat.SPDEF], 2),
);
},
},
],
};
@ -132,9 +142,9 @@ export const TheStrongStuffEncounter: MysteryEncounter =
buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [
{
text: `${namespace}:option.1.selected`
}
]
text: `${namespace}:option.1.selected`,
},
],
},
async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -145,7 +155,9 @@ export const TheStrongStuffEncounter: MysteryEncounter =
// -15 to all base stats of highest BST (halved for HP), +10 to all base stats of rest of party (halved for HP)
// Sort party by bst
const sortedParty = globalScene.getPlayerParty().slice(0)
const sortedParty = globalScene
.getPlayerParty()
.slice(0)
.sort((pokemon1, pokemon2) => {
const pokemon1Bst = pokemon1.getSpeciesForm().getBaseStatTotal();
const pokemon2Bst = pokemon2.getSpeciesForm().getBaseStatTotal();
@ -170,12 +182,12 @@ export const TheStrongStuffEncounter: MysteryEncounter =
encounter.dialogue.outro = [
{
text: `${namespace}:outro`,
}
},
];
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.withSimpleOption(
{
@ -190,24 +202,28 @@ export const TheStrongStuffEncounter: MysteryEncounter =
async () => {
// Pick battle
const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SOUL_DEW ], fillRemaining: true });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW],
fillRemaining: true,
});
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.GASTRO_ACID),
ignorePp: true
ignorePp: true,
},
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.STEALTH_ROCK),
ignorePp: true
});
ignorePp: true,
},
);
encounter.dialogue.outro = [];
await transitionMysteryEncounterIntroVisuals(true, true, 500);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
}
},
)
.build();

View File

@ -1,5 +1,12 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierType,
generateModifierTypeOption,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -13,7 +20,7 @@ import { Abilities } from "#enums/abilities";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { Nature } from "#enums/nature";
import { Type } from "#enums/type";
import { PokemonType } from "#enums/pokemon-type";
import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat";
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms";
@ -36,8 +43,9 @@ const namespace = "mysteryEncounters/theWinstrateChallenge";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3821 | GitHub Issue #3821}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TheWinstrateChallengeEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_WINSTRATE_CHALLENGE)
export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.THE_WINSTRATE_CHALLENGE,
)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withIntroSpriteConfigs([
@ -46,20 +54,20 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
fileRoot: "trainer",
hasShadow: false,
x: 16,
y: -4
y: -4,
},
{
spriteKey: "vivi",
fileRoot: "trainer",
hasShadow: false,
x: -14,
y: -4
y: -4,
},
{
spriteKey: "victor",
fileRoot: "trainer",
hasShadow: true,
x: -32
x: -32,
},
{
spriteKey: "victoria",
@ -73,7 +81,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
hasShadow: true,
x: 3,
y: 5,
yShadow: 5
yShadow: 5,
},
])
.withIntroDialogue([
@ -120,7 +128,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
};
await transitionMysteryEncounterIntroVisuals(true, false);
await spawnNextTrainerOrEndEncounter();
}
},
)
.withSimpleOption(
{
@ -136,9 +144,12 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
async () => {
// Refuse the challenge, they full heal the party and give the player a Rarer Candy
globalScene.unshiftPhase(new PartyHealPhase(true));
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.RARER_CANDY ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY],
fillRemaining: false,
});
leaveEncounterWithoutBattle();
}
},
)
.build();
@ -159,7 +170,10 @@ async function spawnNextTrainerOrEndEncounter() {
globalScene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in
const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!;
machoBrace.type.tier = ModifierTier.MASTER;
setEncounterRewards({ guaranteedModifierTypeOptions: [ machoBrace ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeOptions: [machoBrace],
fillRemaining: false,
});
encounter.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(false, MysteryEncounterMode.NO_BATTLE);
} else {
@ -168,6 +182,7 @@ async function spawnNextTrainerOrEndEncounter() {
}
function endTrainerBattleAndShowDialogue(): Promise<void> {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
return new Promise(async resolve => {
if (globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs.length === 0) {
// Battle is over
@ -182,7 +197,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
duration: 750,
onComplete: () => {
globalScene.field.remove(trainer, true);
}
},
});
}
@ -191,13 +206,19 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
} else {
globalScene.arena.resetArenaEffects();
const playerField = globalScene.getPlayerField();
playerField.forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED));
for (const pokemon of playerField) {
pokemon.lapseTag(BattlerTagType.COMMANDED);
}
playerField.forEach((_, p) => globalScene.unshiftPhase(new ReturnPhase(p)));
for (const pokemon of globalScene.getPlayerParty()) {
// Only trigger form change when Eiscue is in Noice form
// Hardcoded Eiscue for now in case it is fused with another pokemon
if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) {
if (
pokemon.species.speciesId === Species.EISCUE &&
pokemon.hasAbility(Abilities.ICE_FACE) &&
pokemon.formIndex === 1
) {
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger);
}
@ -222,7 +243,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
onComplete: () => {
globalScene.field.remove(trainer, true);
resolve();
}
},
});
}
}
@ -242,14 +263,14 @@ function getVictorTrainerConfig(): EnemyPartyConfig {
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
isTransferable: false,
},
]
],
},
{
species: getPokemonSpecies(Species.OBSTAGOON),
@ -260,16 +281,16 @@ function getVictorTrainerConfig(): EnemyPartyConfig {
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
}
]
isTransferable: false,
},
],
},
],
};
}
@ -286,14 +307,14 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig {
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType,
isTransferable: false
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
isTransferable: false,
},
],
},
{
species: getPokemonSpecies(Species.GARDEVOIR),
@ -303,18 +324,22 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig {
moveSet: [Moves.PSYSHOCK, Moves.MOONBLAST, Moves.SHADOW_BALL, Moves.WILL_O_WISP],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.PSYCHIC ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
PokemonType.PSYCHIC,
]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FAIRY ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
PokemonType.FAIRY,
]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false
}
]
}
]
isTransferable: false,
},
],
},
],
};
}
@ -332,14 +357,14 @@ function getViviTrainerConfig(): EnemyPartyConfig {
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
stackCount: 4,
isTransferable: false
}
]
isTransferable: false,
},
],
},
{
species: getPokemonSpecies(Species.BRELOOM),
@ -351,13 +376,13 @@ function getViviTrainerConfig(): EnemyPartyConfig {
{
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
stackCount: 4,
isTransferable: false
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
isTransferable: false
}
]
isTransferable: false,
},
],
},
{
species: getPokemonSpecies(Species.CAMERUPT),
@ -369,11 +394,11 @@ function getViviTrainerConfig(): EnemyPartyConfig {
{
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 3,
isTransferable: false
isTransferable: false,
},
]
}
]
],
},
],
};
}
@ -390,11 +415,11 @@ function getVickyTrainerConfig(): EnemyPartyConfig {
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType,
isTransferable: false
}
]
}
]
isTransferable: false,
},
],
},
],
};
}
@ -412,9 +437,9 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
{
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
isTransferable: false,
},
],
},
{
species: getPokemonSpecies(Species.SWALOT),
@ -466,8 +491,8 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType,
stackCount: 2,
}
]
},
],
},
{
species: getPokemonSpecies(Species.DODRIO),
@ -479,9 +504,9 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
{
modifier: generateModifierType(modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
isTransferable: false,
},
],
},
{
species: getPokemonSpecies(Species.ALAKAZAM),
@ -493,9 +518,9 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
{
modifier: generateModifierType(modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
isTransferable: false,
},
]
],
},
{
species: getPokemonSpecies(Species.DARMANITAN),
@ -507,10 +532,10 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
{
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
isTransferable: false,
},
]
}
]
],
},
],
};
}

View File

@ -1,7 +1,12 @@
import type { Ability } from "#app/data/ability";
import { allAbilities } from "#app/data/ability";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getNatureName } from "#app/data/nature";
import { speciesStarterCosts } from "#app/data/balance/starters";
import type { PlayerPokemon } from "#app/field/pokemon";
@ -35,8 +40,9 @@ const namespace = "mysteryEncounters/trainingSession";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3802 | GitHub Issue #3802}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TrainingSessionEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRAINING_SESSION)
export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.TRAINING_SESSION,
)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party
@ -50,21 +56,20 @@ export const TrainingSessionEncounter: MysteryEncounter =
hasShadow: true,
y: 6,
x: 5,
yShadow: -2
yShadow: -2,
},
])
.withIntroDialogue([
{
text: `${namespace}:intro`,
}
},
])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
@ -96,10 +101,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Spawn light training session with chosen pokemon
// Every 50 waves, add +1 boss segment, capping at 5
const segments = Math.min(
2 + Math.floor(globalScene.currentBattle.waveIndex / 50),
5
);
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 50), 5);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(playerPokemon, segments, modifiers);
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
@ -128,15 +130,9 @@ export const TrainingSessionEncounter: MysteryEncounter =
const ivToChange = ivIndexes.pop();
let newVal = ivToChange.iv;
if (improvedCount === 0) {
encounter.setDialogueToken(
"stat1",
i18next.t(getStatKey(ivToChange.index)) ?? ""
);
encounter.setDialogueToken("stat1", i18next.t(getStatKey(ivToChange.index)) ?? "");
} else {
encounter.setDialogueToken(
"stat2",
i18next.t(getStatKey(ivToChange.index)) ?? ""
);
encounter.setDialogueToken("stat2", i18next.t(getStatKey(ivToChange.index)) ?? "");
}
// Corrects required encounter breakpoints to be continuous for all IV values
@ -170,11 +166,10 @@ export const TrainingSessionEncounter: MysteryEncounter =
await initBattleWithEnemyConfig(config);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
@ -189,7 +184,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
.withPreOptionPhase(async (): Promise<boolean> => {
// Open menu for selecting pokemon and Nature
const encounter = globalScene.currentBattle.mysteryEncounter!;
const natures = new Array(25).fill(null).map((val, i) => i as Nature);
const natures = new Array(25).fill(null).map((_val, i) => i as Nature);
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for nature selection
return natures.map((nature: Nature) => {
@ -246,11 +241,10 @@ export const TrainingSessionEncounter: MysteryEncounter =
await initBattleWithEnemyConfig(config);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
@ -267,13 +261,13 @@ export const TrainingSessionEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for ability selection
const speciesForm = !!pokemon.getFusionSpeciesForm()
const speciesForm = pokemon.getFusionSpeciesForm()
? pokemon.getFusionSpeciesForm()
: pokemon.getSpeciesForm();
const abilityCount = speciesForm.getAbilityCount();
const abilities: Ability[] = new Array(abilityCount)
.fill(null)
.map((val, i) => allAbilities[speciesForm.getAbility(i)]);
.map((_val, i) => allAbilities[speciesForm.getAbility(i)]);
const optionSelectItems: OptionSelectItem[] = [];
abilities.forEach((ability: Ability, index) => {
@ -317,9 +311,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 30), 6);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(playerPokemon, segments, modifiers);
config.pokemonConfigs![0].tags = [
BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON,
];
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => {
@ -327,15 +319,18 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Add the pokemon back to party with ability change
const abilityIndex = encounter.misc.abilityIndex;
if (!!playerPokemon.getFusionSpeciesForm()) {
if (playerPokemon.getFusionSpeciesForm()) {
playerPokemon.fusionAbilityIndex = abilityIndex;
// Only update the fusion's dex data if the Pokemon is already caught in dex (ignore rentals)
const rootFusionSpecies = playerPokemon.fusionSpecies?.getRootSpeciesId();
if (!isNullOrUndefined(rootFusionSpecies)
&& speciesStarterCosts.hasOwnProperty(rootFusionSpecies)
&& !!globalScene.gameData.dexData[rootFusionSpecies].caughtAttr) {
globalScene.gameData.starterData[rootFusionSpecies].abilityAttr |= playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2
if (
!isNullOrUndefined(rootFusionSpecies) &&
speciesStarterCosts.hasOwnProperty(rootFusionSpecies) &&
!!globalScene.gameData.dexData[rootFusionSpecies].caughtAttr
) {
globalScene.gameData.starterData[rootFusionSpecies].abilityAttr |=
playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2
? 1 << playerPokemon.fusionAbilityIndex
: AbilityAttr.ABILITY_HIDDEN;
}
@ -359,7 +354,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
await initBattleWithEnemyConfig(config);
})
.build()
.build(),
)
.withSimpleOption(
{
@ -375,7 +370,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.build();
@ -384,11 +379,11 @@ function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifier
// Passes modifiers by reference
modifiers.value = playerPokemon.getHeldItems();
const modifierConfigs = modifiers.value.map((mod) => {
const modifierConfigs = modifiers.value.map(mod => {
return {
modifier: mod.clone(),
isTransferable: false,
stackCount: mod.stackCount
stackCount: mod.stackCount,
};
}) as HeldModifierConfig[];
@ -410,6 +405,4 @@ function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifier
class ModifiersHolder {
public value: PokemonHeldItemModifier[] = [];
constructor() {}
}

View File

@ -1,5 +1,12 @@
import type { EnemyPartyConfig, EnemyPokemonConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
loadCustomMovesForEncounter,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -34,8 +41,9 @@ const SHOP_ITEM_COST_MULTIPLIER = 2.5;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3809 | GitHub Issue #3809}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TrashToTreasureEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRASH_TO_TREASURE)
export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.TRASH_TO_TREASURE,
)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(60, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withMaxAllowedEncounters(1)
@ -48,8 +56,8 @@ export const TrashToTreasureEncounter: MysteryEncounter =
disableAnimation: true,
scale: 1.5,
y: 8,
tint: 0.4
}
tint: 0.4,
},
])
.withAutoHideIntroVisuals(false)
.withIntroDialogue([
@ -72,12 +80,12 @@ export const TrashToTreasureEncounter: MysteryEncounter =
shiny: false, // Shiny lock because of custom intro sprite
formIndex: 1, // Gmax
bossSegmentModifier: 1, // +1 Segment from normal
moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ]
moveSet: [Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH],
};
const config: EnemyPartyConfig = {
levelAdditiveModifier: 0.5,
pokemonConfigs: [pokemonConfig],
disableSwitch: true
disableSwitch: true,
};
encounter.enemyPartyConfigs = [config];
@ -92,8 +100,7 @@ export const TrashToTreasureEncounter: MysteryEncounter =
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
@ -112,21 +119,31 @@ export const TrashToTreasureEncounter: MysteryEncounter =
await transitionMysteryEncounterIntroVisuals();
await tryApplyDigRewardItems();
const blackSludge = generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [ SHOP_ITEM_COST_MULTIPLIER ]);
const blackSludge = generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [
SHOP_ITEM_COST_MULTIPLIER,
]);
const modifier = blackSludge?.newModifier();
if (modifier) {
await globalScene.addModifier(modifier, false, false, false, true);
globalScene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 });
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: modifier.type.name }), null, undefined, true);
globalScene.playSound("battle_anims/PRSFX- Venom Drench", {
volume: 2,
});
await showEncounterText(
i18next.t("battle:rewardGain", {
modifierName: modifier.type.name,
}),
null,
undefined,
true,
);
}
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
@ -144,23 +161,27 @@ export const TrashToTreasureEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true });
setEncounterRewards({
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT],
fillRemaining: true,
});
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.TOXIC),
ignorePp: true
ignorePp: true,
},
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY],
move: new PokemonMove(Moves.AMNESIA),
ignorePp: true
});
ignorePp: true,
},
);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
})
.build()
.build(),
)
.build();
@ -173,8 +194,10 @@ async function tryApplyDigRewardItems() {
// Iterate over the party until an item was successfully given
// First leftovers
for (const pokemon of party) {
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const heldItems = globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
true,
) as PokemonHeldItemModifier[];
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
@ -185,8 +208,10 @@ async function tryApplyDigRewardItems() {
// Second leftovers
for (const pokemon of party) {
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const heldItems = globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
true,
) as PokemonHeldItemModifier[];
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
@ -196,12 +221,22 @@ async function tryApplyDigRewardItems() {
}
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGainCount", { modifierName: leftovers.name, count: 2 }), null, undefined, true);
await showEncounterText(
i18next.t("battle:rewardGainCount", {
modifierName: leftovers.name,
count: 2,
}),
null,
undefined,
true,
);
// First Shell bell
for (const pokemon of party) {
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const heldItems = globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
true,
) as PokemonHeldItemModifier[];
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) {
@ -212,8 +247,10 @@ async function tryApplyDigRewardItems() {
// Second Shell bell
for (const pokemon of party) {
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const heldItems = globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
true,
) as PokemonHeldItemModifier[];
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) {
@ -223,7 +260,15 @@ async function tryApplyDigRewardItems() {
}
globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGainCount", { modifierName: shellBell.name, count: 2 }), null, undefined, true);
await showEncounterText(
i18next.t("battle:rewardGainCount", {
modifierName: shellBell.name,
count: 2,
}),
null,
undefined,
true,
);
}
function doGarbageDig() {

View File

@ -1,6 +1,12 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getRandomEncounterSpecies, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
getRandomEncounterSpecies,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterExp,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import type Pokemon from "#app/field/pokemon";
import type { EnemyPokemon } from "#app/field/pokemon";
@ -9,15 +15,22 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoveRequirement, PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import {
MoveRequirement,
PersistentModifierRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import {
catchPokemon,
getHighestLevelPlayerPokemon,
getSpriteKeysFromPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import type { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
import { SelfStatusMove } from "#app/data/move";
import { SelfStatusMove } from "#app/data/moves/move";
import { PokeballType } from "#enums/pokeball";
import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -34,8 +47,9 @@ const namespace = "mysteryEncounters/uncommonBreed";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3811 | GitHub Issue #3811}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const UncommonBreedEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.UNCOMMON_BREED)
export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.UNCOMMON_BREED,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withCatchAllowed(true)
@ -62,7 +76,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
const randomEggMove: Moves = eggMoves[eggMoveIndex];
encounter.misc = {
eggMove: randomEggMove,
pokemon: pokemon
pokemon: pokemon,
};
if (pokemon.moveset.length < 4) {
pokemon.moveset.push(new PokemonMove(randomEggMove));
@ -74,12 +88,14 @@ export const UncommonBreedEncounter: MysteryEncounter =
}
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
[ Stat.DEF, Stat.SPDEF, Stat.SPD ] :
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
globalScene.currentBattle.waveIndex < 50
? [Stat.DEF, Stat.SPDEF, Stat.SPD]
: [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
const config: EnemyPartyConfig = {
pokemonConfigs: [{
pokemonConfigs: [
{
level: level,
species: pokemon.species,
dataSource: new PokemonData(pokemon),
@ -87,9 +103,12 @@ export const UncommonBreedEncounter: MysteryEncounter =
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.stat_boost`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
}
}],
globalScene.unshiftPhase(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1),
);
},
},
],
};
encounter.enemyPartyConfigs = [config];
@ -103,7 +122,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
repeat: true,
isPokemon: true,
isShiny: pokemon.shiny,
variant: pokemon.variant
variant: pokemon.variant,
},
];
@ -124,7 +143,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
yoyo: true,
y: "-=20",
loop: 1,
onComplete: () => encounter.introVisuals?.playShinySparkles()
onComplete: () => encounter.introVisuals?.playShinySparkles(),
});
globalScene.time.delayedCall(500, () => globalScene.playSound("battle_anims/PRSFX- Spotlight2"));
@ -155,22 +174,20 @@ export const UncommonBreedEncounter: MysteryEncounter =
const move = pokemonMove.getMove();
const target = move instanceof SelfStatusMove ? BattlerIndex.ENEMY : BattlerIndex.PLAYER;
encounter.startOfBattleEffects.push(
{
encounter.startOfBattleEffects.push({
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [target],
move: pokemonMove,
ignorePp: true
ignorePp: true,
});
}
setEncounterRewards({ fillRemaining: true });
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
}
},
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
@ -178,16 +195,18 @@ export const UncommonBreedEncounter: MysteryEncounter =
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
selected: [
{
text: `${namespace}:option.2.selected`
}
]
text: `${namespace}:option.2.selected`,
},
],
})
.withOptionPhase(async () => {
// Give it some food
// Remove 4 random berries from player's party
// Get all player berry items, remove from party, and store reference
const berryItems: BerryModifier[] = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
const berryItems: BerryModifier[] = globalScene.findModifiers(
m => m instanceof BerryModifier,
) as BerryModifier[];
for (let i = 0; i < 4; i++) {
const index = randSeedInt(berryItems.length);
const randBerry = berryItems[index];
@ -210,11 +229,10 @@ export const UncommonBreedEncounter: MysteryEncounter =
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle();
})
.build()
.build(),
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
@ -222,9 +240,9 @@ export const UncommonBreedEncounter: MysteryEncounter =
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
selected: [
{
text: `${namespace}:option.3.selected`
}
]
text: `${namespace}:option.3.selected`,
},
],
})
.withOptionPhase(async () => {
// Attract the pokemon with a move
@ -248,7 +266,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle();
})
.build()
.build(),
)
.build();

View File

@ -1,4 +1,4 @@
import { Type } from "#enums/type";
import { PokemonType } from "#enums/pokemon-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import { globalScene } from "#app/global-scene";
@ -6,7 +6,12 @@ import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounte
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils";
import {
generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
} from "../utils/encounter-phase-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type { PlayerPokemon } from "#app/field/pokemon";
@ -23,7 +28,10 @@ import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import i18next from "#app/plugins/i18n";
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
import {
doPokemonTransformationSequence,
TransformationScreenPosition,
} from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
import { getLevelTotalExp } from "#app/data/exp";
import { Stat } from "#enums/stat";
import { Challenges } from "#enums/challenges";
@ -116,8 +124,9 @@ const STANDARD_BST_TRANSFORM_BASE_VALUES: [number, number] = [ 40, 50 ];
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3822 | GitHub Issue #3822}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const WeirdDreamEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.WEIRD_DREAM,
)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION)
// TODO: should reset minimum wave to 10 when there are more Rogue tiers in pool. Matching Dark Deal minimum for now.
@ -129,7 +138,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
hasShadow: true,
y: 11,
yShadow: 6,
x: 4
x: 4,
},
])
.withIntroDialogue([
@ -153,7 +162,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets());
globalScene.currentBattle.mysteryEncounter!.misc = {
teamTransformations,
loadAssets
loadAssets,
};
return true;
@ -163,8 +172,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
@ -172,7 +180,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
selected: [
{
text: `${namespace}:option.1.selected`,
}
},
],
})
.withPreOptionPhase(async () => {
@ -215,10 +223,19 @@ export const WeirdDreamEncounter: MysteryEncounter =
await showEncounterText(`${namespace}:option.1.dream_complete`);
await doNewTeamPostProcess(transformations);
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT, modifierTypes.MINT ], fillRemaining: false });
setEncounterRewards({
guaranteedModifierTypeFuncs: [
modifierTypes.MEMORY_MUSHROOM,
modifierTypes.ROGUE_BALL,
modifierTypes.MINT,
modifierTypes.MINT,
modifierTypes.MINT,
],
fillRemaining: false,
});
leaveEncounterWithoutBattle(true);
})
.build()
.build(),
)
.withSimpleOption(
{
@ -232,7 +249,8 @@ export const WeirdDreamEncounter: MysteryEncounter =
},
async () => {
// Battle your "future" team for some item rewards
const transformations: PokemonTransformation[] = globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations;
const transformations: PokemonTransformation[] =
globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations;
// Uses the pokemon that player's party would have transformed into
const enemyPokemonConfigs: EnemyPokemonConfig[] = [];
@ -251,16 +269,19 @@ export const WeirdDreamEncounter: MysteryEncounter =
newPokemonHeldItemConfigs.push({
modifier: item.clone() as PokemonHeldItemModifier,
stackCount: item.getStackCount(),
isTransferable: false
isTransferable: false,
});
}
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
if (shouldGetOldGateau(newPokemon)) {
const stats = getOldGateauBoostedStats(newPokemon);
newPokemonHeldItemConfigs.push({
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [ OLD_GATEAU_STATS_UP, stats ]) as PokemonHeldItemModifierType,
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [
OLD_GATEAU_STATS_UP,
stats,
]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false
isTransferable: false,
});
}
@ -269,19 +290,22 @@ export const WeirdDreamEncounter: MysteryEncounter =
isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD,
level: previousPokemon.level,
dataSource: dataSource,
modifierConfigs: newPokemonHeldItemConfigs
modifierConfigs: newPokemonHeldItemConfigs,
};
enemyPokemonConfigs.push(enemyConfig);
}
const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET;
const trainerConfig = trainerConfigs[genderIndex === PlayerGender.FEMALE ? TrainerType.FUTURE_SELF_F : TrainerType.FUTURE_SELF_M].clone();
const trainerConfig =
trainerConfigs[
genderIndex === PlayerGender.FEMALE ? TrainerType.FUTURE_SELF_F : TrainerType.FUTURE_SELF_M
].clone();
trainerConfig.setPartyTemplates(new TrainerPartyTemplate(transformations.length, PartyMemberStrength.STRONG));
const enemyPartyConfig: EnemyPartyConfig = {
trainerConfig: trainerConfig,
pokemonConfigs: enemyPokemonConfigs,
female: genderIndex === PlayerGender.FEMALE
female: genderIndex === PlayerGender.FEMALE,
};
const onBeforeRewards = () => {
@ -295,11 +319,25 @@ export const WeirdDreamEncounter: MysteryEncounter =
}
};
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: false }, undefined, onBeforeRewards);
setEncounterRewards(
{
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.GREAT,
ModifierTier.GREAT,
],
fillRemaining: false,
},
undefined,
onBeforeRewards,
);
await showEncounterText(`${namespace}:option.2.selected_2`, null, undefined, true);
await initBattleWithEnemyConfig(enemyPartyConfig);
}
},
)
.withSimpleOption(
{
@ -314,7 +352,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
async () => {
// Leave, reduce party levels by 10%
for (const pokemon of globalScene.getPlayerParty()) {
pokemon.level = Math.max(Math.ceil((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100 * pokemon.level), 1);
pokemon.level = Math.max(Math.ceil(((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100) * pokemon.level), 1);
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
pokemon.levelExp = 0;
@ -325,7 +363,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true);
return true;
}
},
)
.build();
@ -342,7 +380,7 @@ function getTeamTransformations(): PokemonTransformation[] {
const alreadyUsedSpecies: PokemonSpecies[] = party.map(p => p.species);
const pokemonTransformations: PokemonTransformation[] = party.map(p => {
return {
previousPokemon: p
previousPokemon: p,
} as PokemonTransformation;
});
@ -358,7 +396,9 @@ function getTeamTransformations(): PokemonTransformation[] {
for (let i = 0; i < numPokemon; i++) {
const removed = removedPokemon[i];
const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id);
pokemonTransformations[index].heldItems = removed.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
pokemonTransformations[index].heldItems = removed
.getHeldItems()
.filter(m => !(m instanceof PokemonFormChangeItemModifier));
const bst = removed.getSpeciesForm().getBaseStatTotal();
let newBstRange: [number, number];
@ -368,7 +408,13 @@ function getTeamTransformations(): PokemonTransformation[] {
newBstRange = STANDARD_BST_TRANSFORM_BASE_VALUES;
}
const newSpecies = getTransformedSpecies(bst, newBstRange, hasPokemonInSuperLegendaryBstThreshold, hasPokemonInLegendaryBstThreshold, alreadyUsedSpecies);
const newSpecies = getTransformedSpecies(
bst,
newBstRange,
hasPokemonInSuperLegendaryBstThreshold,
hasPokemonInLegendaryBstThreshold,
alreadyUsedSpecies,
);
const newSpeciesBst = newSpecies.getBaseStatTotal();
if (newSpeciesBst > SUPER_LEGENDARY_BST_THRESHOLD) {
@ -378,7 +424,6 @@ function getTeamTransformations(): PokemonTransformation[] {
hasPokemonInLegendaryBstThreshold = true;
}
pokemonTransformations[index].newSpecies = newSpecies;
console.log("New species: " + JSON.stringify(newSpecies));
alreadyUsedSpecies.push(newSpecies);
@ -386,7 +431,12 @@ function getTeamTransformations(): PokemonTransformation[] {
for (const transformation of pokemonTransformations) {
const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount());
transformation.newPokemon = globalScene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined);
transformation.newPokemon = globalScene.addPlayerPokemon(
transformation.newSpecies,
transformation.previousPokemon.level,
newAbilityIndex,
undefined,
);
}
return pokemonTransformations;
@ -411,7 +461,8 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
if (shouldGetOldGateau(newPokemon)) {
const stats = getOldGateauBoostedStats(newPokemon);
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU()
const modType = modifierTypes
.MYSTERY_ENCOUNTER_OLD_GATEAU()
.generateType(globalScene.getPlayerParty(), [OLD_GATEAU_STATS_UP, stats])
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
const modifier = modType?.newModifier(newPokemon);
@ -446,7 +497,12 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
* @param speciesRootForm
* @param forBattle Default `false`. If false, will perform achievements and dex unlocks for the player.
*/
async function postProcessTransformedPokemon(previousPokemon: PlayerPokemon, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise<boolean> {
async function postProcessTransformedPokemon(
previousPokemon: PlayerPokemon,
newPokemon: PlayerPokemon,
speciesRootForm: Species,
forBattle = false,
): Promise<boolean> {
let isNewStarter = false;
// Roll HA a second time
if (newPokemon.species.abilityHidden) {
@ -473,8 +529,14 @@ async function postProcessTransformedPokemon(previousPokemon: PlayerPokemon, new
newPokemon.nature = [Nature.HARDY, Nature.DOCILE, Nature.BASHFUL, Nature.QUIRKY, Nature.SERIOUS][randSeedInt(5)];
// For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it
if (!forBattle && (newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny())) {
if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) {
if (
!forBattle &&
(newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny())
) {
if (
newPokemon.getSpeciesForm().abilityHidden &&
newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1
) {
globalScene.validateAchv(achvs.HIDDEN_ABILITY);
}
@ -494,7 +556,11 @@ async function postProcessTransformedPokemon(previousPokemon: PlayerPokemon, new
const newStarterUnlocked = await globalScene.gameData.setPokemonCaught(newPokemon, true, false, false);
if (newStarterUnlocked) {
isNewStarter = true;
await showEncounterText(i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() }));
await showEncounterText(
i18next.t("battle:addedAsAStarter", {
pokemonName: getPokemonSpecies(speciesRootForm).getName(),
}),
);
}
}
@ -528,10 +594,10 @@ async function postProcessTransformedPokemon(previousPokemon: PlayerPokemon, new
// Randomize the second type of the pokemon
// If the pokemon does not normally have a second type, it will gain 1
const newTypes = [ Type.UNKNOWN ];
let newType = randSeedInt(18) as Type;
const newTypes = [PokemonType.UNKNOWN];
let newType = randSeedInt(18) as PokemonType;
while (newType === newTypes[0]) {
newType = randSeedInt(18) as Type;
newType = randSeedInt(18) as PokemonType;
}
newTypes.push(newType);
if (!newPokemon.customPokemonData) {
@ -568,20 +634,27 @@ function getOldGateauBoostedStats(pokemon: Pokemon): Stat[] {
return stats;
}
function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies {
function getTransformedSpecies(
originalBst: number,
bstSearchRange: [number, number],
hasPokemonBstHigherThan600: boolean,
hasPokemonBstBetween570And600: boolean,
alreadyUsedSpecies: PokemonSpecies[],
): PokemonSpecies {
let newSpecies: PokemonSpecies | undefined;
while (isNullOrUndefined(newSpecies)) {
const bstCap = originalBst + bstSearchRange[1];
const bstMin = Math.max(originalBst + bstSearchRange[0], 0);
// Get any/all species that fall within the Bst range requirements
let validSpecies = allSpecies
.filter(s => {
let validSpecies = allSpecies.filter(s => {
const speciesBst = s.getBaseStatTotal();
const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap;
// Checks that a Pokemon has not already been added in the +600 or 570-600 slots;
const validBst = (!hasPokemonBstBetween570And600 || (speciesBst < NON_LEGENDARY_BST_THRESHOLD || speciesBst > SUPER_LEGENDARY_BST_THRESHOLD)) &&
const validBst =
(!hasPokemonBstBetween570And600 ||
speciesBst < NON_LEGENDARY_BST_THRESHOLD ||
speciesBst > SUPER_LEGENDARY_BST_THRESHOLD) &&
(!hasPokemonBstHigherThan600 || speciesBst <= SUPER_LEGENDARY_BST_THRESHOLD);
return bstInRange && validBst && !EXCLUDED_TRANSFORMATION_SPECIES.includes(s.speciesId);
});
@ -608,7 +681,13 @@ function doShowDreamBackground() {
transformationContainer.name = "Dream Background";
// In case it takes a bit for video to load
const transformationStaticBg = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0);
const transformationStaticBg = globalScene.add.rectangle(
0,
0,
globalScene.game.canvas.width / 6,
globalScene.game.canvas.height / 6,
0,
);
transformationStaticBg.setName("Black Background");
transformationStaticBg.setOrigin(0, 0);
transformationContainer.add(transformationStaticBg);
@ -631,7 +710,7 @@ function doShowDreamBackground() {
targets: transformationContainer,
alpha: 1,
duration: 3000,
ease: "Sine.easeInOut"
ease: "Sine.easeInOut",
});
}
@ -645,7 +724,7 @@ function doHideDreamBackground() {
ease: "Sine.easeInOut",
onComplete: () => {
globalScene.fieldUI.remove(transformationContainer, true);
}
},
});
}
@ -660,8 +739,7 @@ function doSideBySideTransformations(transformations: PokemonTransformation[]) {
const pokemon2 = transformation.newPokemon;
const screenPosition = i as TransformationScreenPosition;
const transformationPromise = doPokemonTransformationSequence(pokemon1, pokemon2, screenPosition)
.then(() => {
const transformationPromise = doPokemonTransformationSequence(pokemon1, pokemon2, screenPosition).then(() => {
if (transformations.length > i + 3) {
const nextTransformationAtPosition = transformations[i + 3];
const nextPokemon1 = nextTransformationAtPosition.previousPokemon;
@ -691,7 +769,11 @@ function doSideBySideTransformations(transformations: PokemonTransformation[]) {
* @param newPokemon
* @param speciesRootForm
*/
async function addEggMoveToNewPokemonMoveset(newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise<number | null> {
async function addEggMoveToNewPokemonMoveset(
newPokemon: PlayerPokemon,
speciesRootForm: Species,
forBattle = false,
): Promise<number | null> {
let eggMoveIndex: null | number = null;
const eggMoves = newPokemon.getEggMoves()?.slice(0);
if (eggMoves) {
@ -717,7 +799,11 @@ async function addEggMoveToNewPokemonMoveset(newPokemon: PlayerPokemon, speciesR
}
// For pokemon that the player owns (including ones just caught), unlock the egg move
if (!forBattle && !isNullOrUndefined(randomEggMoveIndex) && !!globalScene.gameData.dexData[speciesRootForm].caughtAttr) {
if (
!forBattle &&
!isNullOrUndefined(randomEggMoveIndex) &&
!!globalScene.gameData.dexData[speciesRootForm].caughtAttr
) {
await globalScene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true);
}
}
@ -732,11 +818,18 @@ async function addEggMoveToNewPokemonMoveset(newPokemon: PlayerPokemon, speciesR
* @param newPokemonGeneratedMoveset
* @param newEggMoveIndex
*/
function addFavoredMoveToNewPokemonMoveset(newPokemon: PlayerPokemon, newPokemonGeneratedMoveset: (PokemonMove | null)[], newEggMoveIndex: number | null) {
function addFavoredMoveToNewPokemonMoveset(
newPokemon: PlayerPokemon,
newPokemonGeneratedMoveset: (PokemonMove | null)[],
newEggMoveIndex: number | null,
) {
let favoredMove: PokemonMove | null = null;
for (const move of newPokemonGeneratedMoveset) {
// Needs to match first type, second type will be replaced
if (move?.getMove().type === newPokemon.getTypes()[0] && !newPokemon.moveset.some(m => m?.moveId === move?.moveId)) {
if (
move?.getMove().type === newPokemon.getTypes()[0] &&
!newPokemon.moveset.some(m => m?.moveId === move?.moveId)
) {
favoredMove = move;
break;
}

View File

@ -72,4 +72,3 @@ export default class MysteryEncounterDialogue {
encounterOptionsDialogue?: EncounterOptionsDialogue;
outro?: TextDisplay[];
}

View File

@ -3,14 +3,19 @@ import type { Moves } from "#app/enums/moves";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import type { Type } from "#enums/type";
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import type { PokemonType } from "#enums/pokemon-type";
import {
EncounterPokemonRequirement,
EncounterSceneRequirement,
MoneyRequirement,
TypeRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import type { CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
import { CanLearnMoveRequirement } from "./requirements/can-learn-move-requirement";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
// biome-ignore lint/suspicious/noConfusingVoidType: void unions in callbacks are OK
export type OptionPhaseCallback = () => Promise<void | boolean>;
/**
@ -71,16 +76,22 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
* Returns true if option contains any {@linkcode EncounterRequirement}s, false otherwise.
*/
hasRequirements(): boolean {
return this.requirements.length > 0 || this.primaryPokemonRequirements.length > 0 || this.secondaryPokemonRequirements.length > 0;
return (
this.requirements.length > 0 ||
this.primaryPokemonRequirements.length > 0 ||
this.secondaryPokemonRequirements.length > 0
);
}
/**
* Returns true if all {@linkcode EncounterRequirement}s for the option are met
*/
meetsRequirements(): boolean {
return !this.requirements.some(requirement => !requirement.meetsRequirement())
&& this.meetsSupportingRequirementAndSupportingPokemonSelected()
&& this.meetsPrimaryRequirementAndPrimaryPokemonSelected();
return (
!this.requirements.some(requirement => !requirement.meetsRequirement()) &&
this.meetsSupportingRequirementAndSupportingPokemonSelected() &&
this.meetsPrimaryRequirementAndPrimaryPokemonSelected()
);
}
/**
@ -88,7 +99,13 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
* @param pokemon
*/
pokemonMeetsPrimaryRequirements(pokemon: Pokemon): boolean {
return !this.primaryPokemonRequirements.some(req => !req.queryParty(globalScene.getPlayerParty()).map(p => p.id).includes(pokemon.id));
return !this.primaryPokemonRequirements.some(
req =>
!req
.queryParty(globalScene.getPlayerParty())
.map(p => p.id)
.includes(pokemon.id),
);
}
/**
@ -125,28 +142,27 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
} else {
overlap.push(qp);
}
}
if (truePrimaryPool.length > 0) {
// always choose from the non-overlapping pokemon first
this.primaryPokemon = truePrimaryPool[randSeedInt(truePrimaryPool.length)];
return true;
} else {
}
// if there are multiple overlapping pokemon, we're okay - just choose one and take it out of the supporting pokemon pool
if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) {
if (overlap.length > 1 || this.secondaryPokemon.length - overlap.length >= 1) {
this.primaryPokemon = overlap[randSeedInt(overlap.length)];
this.secondaryPokemon = this.secondaryPokemon.filter((supp) => supp !== this.primaryPokemon);
this.secondaryPokemon = this.secondaryPokemon.filter(supp => supp !== this.primaryPokemon);
return true;
}
console.log("Mystery Encounter Edge Case: Requirement not met due to primay pokemon overlapping with support pokemon. There's no valid primary pokemon left.");
console.log(
"Mystery Encounter Edge Case: Requirement not met due to primay pokemon overlapping with support pokemon. There's no valid primary pokemon left.",
);
return false;
}
} else {
// Just pick the first qualifying Pokemon
this.primaryPokemon = qualified[0];
return true;
}
}
/**
* Returns true if all SECONDARY {@linkcode EncounterRequirement}s for the option are met,
@ -180,12 +196,14 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
requirements: EncounterSceneRequirement[] = [];
primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
secondaryPokemonRequirements: EncounterPokemonRequirement[] = [];
excludePrimaryFromSecondaryRequirements: boolean = false;
isDisabledOnRequirementsNotMet: boolean = true;
hasDexProgress: boolean = false;
excludePrimaryFromSecondaryRequirements = false;
isDisabledOnRequirementsNotMet = true;
hasDexProgress = false;
dialogue?: OptionTextDisplay;
static newOptionWithMode(optionMode: MysteryEncounterOptionMode): MysteryEncounterOptionBuilder & Pick<IMysteryEncounterOption, "optionMode"> {
static newOptionWithMode(
optionMode: MysteryEncounterOptionMode,
): MysteryEncounterOptionBuilder & Pick<IMysteryEncounterOption, "optionMode"> {
return Object.assign(new MysteryEncounterOptionBuilder(), { optionMode });
}
@ -197,7 +215,9 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
* Adds a {@linkcode EncounterSceneRequirement} to {@linkcode requirements}
* @param requirement
*/
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounterOption, "requirements">> {
withSceneRequirement(
requirement: EncounterSceneRequirement,
): this & Required<Pick<IMysteryEncounterOption, "requirements">> {
if (requirement instanceof EncounterPokemonRequirement) {
Error("Incorrectly added pokemon requirement as scene requirement.");
}
@ -216,7 +236,9 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
* If there are scenarios where the Encounter should NOT continue, should return boolean instead of void.
* @param onPreOptionPhase
*/
withPreOptionPhase(onPreOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPreOptionPhase">> {
withPreOptionPhase(
onPreOptionPhase: OptionPhaseCallback,
): this & Required<Pick<IMysteryEncounterOption, "onPreOptionPhase">> {
return Object.assign(this, { onPreOptionPhase: onPreOptionPhase });
}
@ -228,7 +250,9 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
return Object.assign(this, { onOptionPhase: onOptionPhase });
}
withPostOptionPhase(onPostOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPostOptionPhase">> {
withPostOptionPhase(
onPostOptionPhase: OptionPhaseCallback,
): this & Required<Pick<IMysteryEncounterOption, "onPostOptionPhase">> {
return Object.assign(this, { onPostOptionPhase: onPostOptionPhase });
}
@ -236,13 +260,17 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
* Adds a {@linkcode EncounterPokemonRequirement} to {@linkcode primaryPokemonRequirements}
* @param requirement
*/
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounterOption, "primaryPokemonRequirements">> {
withPrimaryPokemonRequirement(
requirement: EncounterPokemonRequirement,
): this & Required<Pick<IMysteryEncounterOption, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
this.primaryPokemonRequirements.push(requirement);
return Object.assign(this, { primaryPokemonRequirements: this.primaryPokemonRequirements });
return Object.assign(this, {
primaryPokemonRequirements: this.primaryPokemonRequirements,
});
}
/**
@ -254,8 +282,15 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
* @param invertQuery
* @returns
*/
withPokemonTypeRequirement(type: Type | Type[], excludeFainted?: boolean, minNumberOfPokemon?: number, invertQuery?: boolean) {
return this.withPrimaryPokemonRequirement(new TypeRequirement(type, excludeFainted, minNumberOfPokemon, invertQuery));
withPokemonTypeRequirement(
type: PokemonType | PokemonType[],
excludeFainted?: boolean,
minNumberOfPokemon?: number,
invertQuery?: boolean,
) {
return this.withPrimaryPokemonRequirement(
new TypeRequirement(type, excludeFainted, minNumberOfPokemon, invertQuery),
);
}
/**
@ -274,14 +309,19 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
* @param requirement
* @param excludePrimaryFromSecondaryRequirements
*/
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = true): this & Required<Pick<IMysteryEncounterOption, "secondaryPokemonRequirements">> {
withSecondaryPokemonRequirement(
requirement: EncounterPokemonRequirement,
excludePrimaryFromSecondaryRequirements = true,
): this & Required<Pick<IMysteryEncounterOption, "secondaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
this.secondaryPokemonRequirements.push(requirement);
this.excludePrimaryFromSecondaryRequirements = excludePrimaryFromSecondaryRequirements;
return Object.assign(this, { secondaryPokemonRequirements: this.secondaryPokemonRequirements });
return Object.assign(this, {
secondaryPokemonRequirements: this.secondaryPokemonRequirements,
});
}
/**

View File

@ -4,7 +4,7 @@ import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evol
import { Nature } from "#enums/nature";
import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms";
import { StatusEffect } from "#enums/status-effect";
import { Type } from "#enums/type";
import { PokemonType } from "#enums/pokemon-type";
import { WeatherType } from "#enums/weather-type";
import type { PlayerPokemon } from "#app/field/pokemon";
import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
@ -76,7 +76,7 @@ export class CombinationSceneRequirement extends EncounterSceneRequirement {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
if (this.isAnd) {
throw new Error("Not implemented (Sorry)");
} else {
}
for (const req of this.requirements) {
if (req.meetsRequirement()) {
return req.getDialogueToken(pokemon);
@ -86,7 +86,6 @@ export class CombinationSceneRequirement extends EncounterSceneRequirement {
return this.requirements[0].getDialogueToken(pokemon);
}
}
}
export abstract class EncounterPokemonRequirement implements EncounterRequirement {
public minNumberOfPokemon: number;
@ -153,11 +152,10 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (this.isAnd) {
return this.requirements.reduce((relevantPokemon, req) => req.queryParty(relevantPokemon), partyPokemon);
} else {
}
const matchingRequirement = this.requirements.find(req => req.queryParty(partyPokemon).length > 0);
return matchingRequirement ? matchingRequirement.queryParty(partyPokemon) : [];
}
}
/**
* Retrieves a dialogue token key/value pair for the given {@linkcode EncounterPokemonRequirement | requirements}.
@ -168,7 +166,7 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
if (this.isAnd) {
throw new Error("Not implemented (Sorry)");
} else {
}
for (const req of this.requirements) {
if (req.meetsRequirement()) {
return req.getDialogueToken(pokemon);
@ -178,7 +176,6 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
return this.requirements[0].getDialogueToken(pokemon);
}
}
}
export class PreviousEncounterRequirement extends EncounterSceneRequirement {
previousEncounterRequirement: MysteryEncounterType;
@ -193,11 +190,18 @@ export class PreviousEncounterRequirement extends EncounterSceneRequirement {
}
override meetsRequirement(): boolean {
return globalScene.mysteryEncounterSaveData.encounteredEvents.some(e => e.type === this.previousEncounterRequirement);
return globalScene.mysteryEncounterSaveData.encounteredEvents.some(
e => e.type === this.previousEncounterRequirement,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return [ "previousEncounter", globalScene.mysteryEncounterSaveData.encounteredEvents.find(e => e.type === this.previousEncounterRequirement)?.[0].toString() ?? "" ];
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return [
"previousEncounter",
globalScene.mysteryEncounterSaveData.encounteredEvents
.find(e => e.type === this.previousEncounterRequirement)?.[0]
.toString() ?? "",
];
}
}
@ -217,14 +221,17 @@ export class WaveRangeRequirement extends EncounterSceneRequirement {
override meetsRequirement(): boolean {
if (!isNullOrUndefined(this.waveRange) && this.waveRange[0] <= this.waveRange[1]) {
const waveIndex = globalScene.currentBattle.waveIndex;
if (waveIndex >= 0 && (this.waveRange[0] >= 0 && this.waveRange[0] > waveIndex) || (this.waveRange[1] >= 0 && this.waveRange[1] < waveIndex)) {
if (
(waveIndex >= 0 && this.waveRange[0] >= 0 && this.waveRange[0] > waveIndex) ||
(this.waveRange[1] >= 0 && this.waveRange[1] < waveIndex)
) {
return false;
}
}
return true;
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return ["waveIndex", globalScene.currentBattle.waveIndex.toString()];
}
}
@ -253,7 +260,7 @@ export class WaveModulusRequirement extends EncounterSceneRequirement {
return this.waveModuli.includes(globalScene.currentBattle.waveIndex % this.modulusValue);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return ["waveIndex", globalScene.currentBattle.waveIndex.toString()];
}
}
@ -268,14 +275,18 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
override meetsRequirement(): boolean {
const timeOfDay = globalScene.arena?.getTimeOfDay();
if (!isNullOrUndefined(timeOfDay) && this.requiredTimeOfDay?.length > 0 && !this.requiredTimeOfDay.includes(timeOfDay)) {
if (
!isNullOrUndefined(timeOfDay) &&
this.requiredTimeOfDay?.length > 0 &&
!this.requiredTimeOfDay.includes(timeOfDay)
) {
return false;
}
return true;
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return ["timeOfDay", TimeOfDay[globalScene.arena.getTimeOfDay()].toLocaleLowerCase()];
}
}
@ -290,14 +301,18 @@ export class WeatherRequirement extends EncounterSceneRequirement {
override meetsRequirement(): boolean {
const currentWeather = globalScene.arena.weather?.weatherType;
if (!isNullOrUndefined(currentWeather) && this.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather!)) {
if (
!isNullOrUndefined(currentWeather) &&
this.requiredWeather?.length > 0 &&
!this.requiredWeather.includes(currentWeather!)
) {
return false;
}
return true;
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
const currentWeather = globalScene.arena.weather?.weatherType;
let token = "";
if (!isNullOrUndefined(currentWeather)) {
@ -325,8 +340,13 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
override meetsRequirement(): boolean {
if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange[0] <= this.partySizeRange[1]) {
const partySize = this.excludeDisallowedPokemon ? globalScene.getPokemonAllowedInBattle().length : globalScene.getPlayerParty().length;
if (partySize >= 0 && (this.partySizeRange[0] >= 0 && this.partySizeRange[0] > partySize) || (this.partySizeRange[1] >= 0 && this.partySizeRange[1] < partySize)) {
const partySize = this.excludeDisallowedPokemon
? globalScene.getPokemonAllowedInBattle().length
: globalScene.getPlayerParty().length;
if (
(partySize >= 0 && this.partySizeRange[0] >= 0 && this.partySizeRange[0] > partySize) ||
(this.partySizeRange[1] >= 0 && this.partySizeRange[1] < partySize)
) {
return false;
}
}
@ -334,7 +354,7 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
return true;
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return ["partySize", globalScene.getPlayerParty().length.toString()];
}
}
@ -343,7 +363,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
requiredHeldItemModifiers: string[];
minNumberOfItems: number;
constructor(heldItem: string | string[], minNumberOfItems: number = 1) {
constructor(heldItem: string | string[], minNumberOfItems = 1) {
super();
this.minNumberOfItems = minNumberOfItems;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
@ -355,19 +375,19 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
return false;
}
let modifierCount = 0;
this.requiredHeldItemModifiers.forEach(modifier => {
for (const modifier of this.requiredHeldItemModifiers) {
const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier);
if (matchingMods?.length > 0) {
matchingMods.forEach(matchingMod => {
for (const matchingMod of matchingMods) {
modifierCount += matchingMod.stackCount;
});
}
});
}
}
return modifierCount >= this.minNumberOfItems;
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return ["requiredItem", this.requiredHeldItemModifiers[0]];
}
}
@ -394,8 +414,11 @@ export class MoneyRequirement extends EncounterSceneRequirement {
return !(this.requiredMoney > 0 && this.requiredMoney > money);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const value = this.scalingMultiplier > 0 ? globalScene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString();
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
const value =
this.scalingMultiplier > 0
? globalScene.getWaveMoneyAmount(this.scalingMultiplier).toString()
: this.requiredMoney.toString();
return ["money", value];
}
}
@ -405,7 +428,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(species: Species | Species[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(species: Species | Species[], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -422,11 +445,14 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredSpecies.filter((species) => pokemon.species.speciesId === species).length > 0);
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed speciess
return partyPokemon.filter((pokemon) => this.requiredSpecies.filter((species) => pokemon.species.speciesId === species).length === 0);
return partyPokemon.filter(
pokemon => this.requiredSpecies.filter(species => pokemon.species.speciesId === species).length > 0,
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed speciess
return partyPokemon.filter(
pokemon => this.requiredSpecies.filter(species => pokemon.species.speciesId === species).length === 0,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -437,13 +463,12 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
}
}
export class NatureRequirement extends EncounterPokemonRequirement {
requiredNature: Nature[];
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(nature: Nature | Nature[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(nature: Nature | Nature[], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -460,11 +485,10 @@ export class NatureRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredNature.filter((nature) => pokemon.nature === nature).length > 0);
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed natures
return partyPokemon.filter((pokemon) => this.requiredNature.filter((nature) => pokemon.nature === nature).length === 0);
return partyPokemon.filter(pokemon => this.requiredNature.filter(nature => pokemon.nature === nature).length > 0);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed natures
return partyPokemon.filter(pokemon => this.requiredNature.filter(nature => pokemon.nature === nature).length === 0);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -476,12 +500,12 @@ export class NatureRequirement extends EncounterPokemonRequirement {
}
export class TypeRequirement extends EncounterPokemonRequirement {
requiredType: Type[];
requiredType: PokemonType[];
excludeFainted: boolean;
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(type: Type | Type[], excludeFainted: boolean = true, minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(type: PokemonType | PokemonType[], excludeFainted = true, minNumberOfPokemon = 1, invertQuery = false) {
super();
this.excludeFainted = excludeFainted;
this.minNumberOfPokemon = minNumberOfPokemon;
@ -497,7 +521,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
}
if (this.excludeFainted) {
partyPokemon = partyPokemon.filter((pokemon) => !pokemon.isFainted());
partyPokemon = partyPokemon.filter(pokemon => !pokemon.isFainted());
}
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
@ -505,30 +529,32 @@ export class TypeRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredType.filter((type) => pokemon.getTypes().includes(type)).length > 0);
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed types
return partyPokemon.filter((pokemon) => this.requiredType.filter((type) => pokemon.getTypes().includes(type)).length === 0);
return partyPokemon.filter(
pokemon => this.requiredType.filter(type => pokemon.getTypes().includes(type)).length > 0,
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed types
return partyPokemon.filter(
pokemon => this.requiredType.filter(type => pokemon.getTypes().includes(type)).length === 0,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const includedTypes = this.requiredType.filter((ty) => pokemon?.getTypes().includes(ty));
const includedTypes = this.requiredType.filter(ty => pokemon?.getTypes().includes(ty));
if (includedTypes.length > 0) {
return [ "type", Type[includedTypes[0]] ];
return ["type", PokemonType[includedTypes[0]]];
}
return ["type", ""];
}
}
export class MoveRequirement extends EncounterPokemonRequirement {
requiredMoves: Moves[] = [];
minNumberOfPokemon: number;
invertQuery: boolean;
excludeDisallowedPokemon: boolean;
constructor(moves: Moves | Moves[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(moves: Moves | Moves[], excludeDisallowedPokemon: boolean, minNumberOfPokemon = 1, invertQuery = false) {
super();
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon;
@ -547,25 +573,27 @@ export class MoveRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
// get the Pokemon with at least one move in the required moves list
return partyPokemon.filter((pokemon) =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
&& pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId)));
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves
return partyPokemon.filter((pokemon) =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
&& !pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId)));
return partyPokemon.filter(
pokemon =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) &&
pokemon.moveset.some(move => move?.moveId && this.requiredMoves.includes(move.moveId)),
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves
return partyPokemon.filter(
pokemon =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) &&
!pokemon.moveset.some(move => move?.moveId && this.requiredMoves.includes(move.moveId)),
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const includedMoves = pokemon?.moveset.filter((move) => move?.moveId && this.requiredMoves.includes(move.moveId));
const includedMoves = pokemon?.moveset.filter(move => move?.moveId && this.requiredMoves.includes(move.moveId));
if (includedMoves && includedMoves.length > 0 && includedMoves[0]) {
return ["move", includedMoves[0].getName()];
}
return ["move", ""];
}
}
/**
@ -578,7 +606,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(learnableMove: Moves | Moves[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(learnableMove: Moves | Moves[], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -595,21 +623,31 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((learnableMove) => pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m?.moveId === tm)).includes(learnableMove)).length > 0);
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed learnableMoves
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((learnableMove) => pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m?.moveId === tm)).includes(learnableMove)).length === 0);
return partyPokemon.filter(
pokemon =>
this.requiredMoves.filter(learnableMove =>
pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m?.moveId === tm)).includes(learnableMove),
).length > 0,
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed learnableMoves
return partyPokemon.filter(
pokemon =>
this.requiredMoves.filter(learnableMove =>
pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m?.moveId === tm)).includes(learnableMove),
).length === 0,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const includedCompatMoves = this.requiredMoves.filter((reqMove) => pokemon?.compatibleTms.filter((tm) => !pokemon.moveset.find(m => m?.moveId === tm)).includes(reqMove));
const includedCompatMoves = this.requiredMoves.filter(reqMove =>
pokemon?.compatibleTms.filter(tm => !pokemon.moveset.find(m => m?.moveId === tm)).includes(reqMove),
);
if (includedCompatMoves.length > 0) {
return ["compatibleMove", Moves[includedCompatMoves[0]]];
}
return ["compatibleMove", ""];
}
}
export class AbilityRequirement extends EncounterPokemonRequirement {
@ -618,7 +656,12 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
invertQuery: boolean;
excludeDisallowedPokemon: boolean;
constructor(abilities: Abilities | Abilities[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(
abilities: Abilities | Abilities[],
excludeDisallowedPokemon: boolean,
minNumberOfPokemon = 1,
invertQuery = false,
) {
super();
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon;
@ -636,15 +679,18 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
&& this.requiredAbilities.some((ability) => pokemon.hasAbility(ability, false)));
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilities
return partyPokemon.filter((pokemon) =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle())
&& this.requiredAbilities.filter((ability) => pokemon.hasAbility(ability, false)).length === 0);
return partyPokemon.filter(
pokemon =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) &&
this.requiredAbilities.some(ability => pokemon.hasAbility(ability, false)),
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilities
return partyPokemon.filter(
pokemon =>
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) &&
this.requiredAbilities.filter(ability => pokemon.hasAbility(ability, false)).length === 0,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -661,7 +707,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -680,35 +726,42 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => {
return this.requiredStatusEffect.some((statusEffect) => {
return partyPokemon.filter(pokemon => {
return this.requiredStatusEffect.some(statusEffect => {
if (statusEffect === StatusEffect.NONE) {
// StatusEffect.NONE also checks for null or undefined status
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === statusEffect;
} else {
return pokemon.status?.effect === statusEffect;
return (
isNullOrUndefined(pokemon.status) ||
isNullOrUndefined(pokemon.status.effect) ||
pokemon.status.effect === statusEffect
);
}
return pokemon.status?.effect === statusEffect;
});
});
} else {
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed StatusEffects
return partyPokemon.filter((pokemon) => {
return !this.requiredStatusEffect.some((statusEffect) => {
return partyPokemon.filter(pokemon => {
return !this.requiredStatusEffect.some(statusEffect => {
if (statusEffect === StatusEffect.NONE) {
// StatusEffect.NONE also checks for null or undefined status
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === statusEffect;
} else {
return (
isNullOrUndefined(pokemon.status) ||
isNullOrUndefined(pokemon.status.effect) ||
pokemon.status.effect === statusEffect
);
}
return pokemon.status?.effect === statusEffect;
}
});
});
}
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const reqStatus = this.requiredStatusEffect.filter((a) => {
const reqStatus = this.requiredStatusEffect.filter(a => {
if (a === StatusEffect.NONE) {
return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === a;
return (
isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === a
);
}
return pokemon!.status?.effect === a;
});
@ -717,7 +770,6 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
}
return ["status", ""];
}
}
/**
@ -730,7 +782,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(formChangeItem: FormChangeItem | FormChangeItem[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(formChangeItem: FormChangeItem | FormChangeItem[], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -746,35 +798,44 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
}
filterByForm(pokemon, formChangeItem) {
if (pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId)
if (
pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) &&
// Get all form changes for this species with an item trigger, including any compound triggers
&& pokemonFormChanges[pokemon.species.speciesId].filter(fc => fc.trigger.hasTriggerType(SpeciesFormChangeItemTrigger))
pokemonFormChanges[pokemon.species.speciesId]
.filter(fc => fc.trigger.hasTriggerType(SpeciesFormChangeItemTrigger))
// Returns true if any form changes match this item
.map(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger)
.flat().flatMap(fc => fc.item).includes(formChangeItem)) {
.flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger)
.flatMap(fc => fc.item)
.includes(formChangeItem)
) {
return true;
} else {
return false;
}
return false;
}
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredFormChangeItem.filter((formChangeItem) => this.filterByForm(pokemon, formChangeItem)).length > 0);
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed formChangeItems
return partyPokemon.filter((pokemon) => this.requiredFormChangeItem.filter((formChangeItem) => this.filterByForm(pokemon, formChangeItem)).length === 0);
return partyPokemon.filter(
pokemon =>
this.requiredFormChangeItem.filter(formChangeItem => this.filterByForm(pokemon, formChangeItem)).length > 0,
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed formChangeItems
return partyPokemon.filter(
pokemon =>
this.requiredFormChangeItem.filter(formChangeItem => this.filterByForm(pokemon, formChangeItem)).length === 0,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = this.requiredFormChangeItem.filter((formChangeItem) => this.filterByForm(pokemon, formChangeItem));
const requiredItems = this.requiredFormChangeItem.filter(formChangeItem =>
this.filterByForm(pokemon, formChangeItem),
);
if (requiredItems.length > 0) {
return ["formChangeItem", FormChangeItem[requiredItems[0]]];
}
return ["formChangeItem", ""];
}
}
export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
@ -782,7 +843,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(evolutionItems: EvolutionItem | EvolutionItem[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(evolutionItems: EvolutionItem | EvolutionItem[], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -798,11 +859,23 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
}
filterByEvo(pokemon, evolutionItem) {
if (pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && pokemonEvolutions[pokemon.species.speciesId].filter(e => e.item === evolutionItem
&& (!e.condition || e.condition.predicate(pokemon))).length && (pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX)) {
if (
pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) &&
pokemonEvolutions[pokemon.species.speciesId].filter(
e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)),
).length &&
pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX
) {
return true;
} else if (pokemon.isFusion() && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(e => e.item === evolutionItem
&& (!e.condition || e.condition.predicate(pokemon))).length && (pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX)) {
}
if (
pokemon.isFusion() &&
pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) &&
pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(
e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)),
).length &&
pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX
) {
return true;
}
return false;
@ -810,15 +883,20 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredEvolutionItem.filter((evolutionItem) => this.filterByEvo(pokemon, evolutionItem)).length > 0);
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed evolutionItemss
return partyPokemon.filter((pokemon) => this.requiredEvolutionItem.filter((evolutionItems) => this.filterByEvo(pokemon, evolutionItems)).length === 0);
return partyPokemon.filter(
pokemon =>
this.requiredEvolutionItem.filter(evolutionItem => this.filterByEvo(pokemon, evolutionItem)).length > 0,
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed evolutionItemss
return partyPokemon.filter(
pokemon =>
this.requiredEvolutionItem.filter(evolutionItems => this.filterByEvo(pokemon, evolutionItems)).length === 0,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = this.requiredEvolutionItem.filter((evoItem) => this.filterByEvo(pokemon, evoItem));
const requiredItems = this.requiredEvolutionItem.filter(evoItem => this.filterByEvo(pokemon, evoItem));
if (requiredItems.length > 0) {
return ["evolutionItem", EvolutionItem[requiredItems[0]]];
}
@ -832,7 +910,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
invertQuery: boolean;
requireTransferable: boolean;
constructor(heldItem: string | string[], minNumberOfPokemon: number = 1, invertQuery: boolean = false, requireTransferable: boolean = true) {
constructor(heldItem: string | string[], minNumberOfPokemon = 1, invertQuery = false, requireTransferable = true) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -850,25 +928,33 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredHeldItemModifiers.some((heldItem) => {
return pokemon.getHeldItems().some((it) => {
return partyPokemon.filter(pokemon =>
this.requiredHeldItemModifiers.some(heldItem => {
return pokemon.getHeldItems().some(it => {
return it.constructor.name === heldItem && (!this.requireTransferable || it.isTransferable);
});
}));
} else {
}),
);
}
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
// E.g. functions as a blacklist
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => {
return !this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem)
&& (!this.requireTransferable || it.isTransferable);
}).length > 0);
}
return partyPokemon.filter(
pokemon =>
pokemon.getHeldItems().filter(it => {
return (
!this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) &&
(!this.requireTransferable || it.isTransferable)
);
}).length > 0,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = pokemon?.getHeldItems().filter((it) => {
return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem)
&& (!this.requireTransferable || it.isTransferable);
const requiredItems = pokemon?.getHeldItems().filter(it => {
return (
this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) &&
(!this.requireTransferable || it.isTransferable)
);
});
if (requiredItems && requiredItems.length > 0) {
return ["heldItem", requiredItems[0].type.name];
@ -878,12 +964,17 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
}
export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRequirement {
requiredHeldItemTypes: Type[];
requiredHeldItemTypes: PokemonType[];
minNumberOfPokemon: number;
invertQuery: boolean;
requireTransferable: boolean;
constructor(heldItemTypes: Type | Type[], minNumberOfPokemon: number = 1, invertQuery: boolean = false, requireTransferable: boolean = true) {
constructor(
heldItemTypes: PokemonType | PokemonType[],
minNumberOfPokemon = 1,
invertQuery = false,
requireTransferable = true,
) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -901,31 +992,43 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredHeldItemTypes.some((heldItemType) => {
return pokemon.getHeldItems().some((it) => {
return it instanceof AttackTypeBoosterModifier
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType
&& (!this.requireTransferable || it.isTransferable);
return partyPokemon.filter(pokemon =>
this.requiredHeldItemTypes.some(heldItemType => {
return pokemon.getHeldItems().some(it => {
return (
it instanceof AttackTypeBoosterModifier &&
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType &&
(!this.requireTransferable || it.isTransferable)
);
});
}));
} else {
}),
);
}
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
// E.g. functions as a blacklist
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => {
return !this.requiredHeldItemTypes.some(heldItemType =>
it instanceof AttackTypeBoosterModifier
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType
&& (!this.requireTransferable || it.isTransferable));
}).length > 0);
}
return partyPokemon.filter(
pokemon =>
pokemon.getHeldItems().filter(it => {
return !this.requiredHeldItemTypes.some(
heldItemType =>
it instanceof AttackTypeBoosterModifier &&
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType &&
(!this.requireTransferable || it.isTransferable),
);
}).length > 0,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = pokemon?.getHeldItems().filter((it) => {
return this.requiredHeldItemTypes.some(heldItemType =>
it instanceof AttackTypeBoosterModifier
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType)
&& (!this.requireTransferable || it.isTransferable);
const requiredItems = pokemon?.getHeldItems().filter(it => {
return (
this.requiredHeldItemTypes.some(
heldItemType =>
it instanceof AttackTypeBoosterModifier &&
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType,
) &&
(!this.requireTransferable || it.isTransferable)
);
});
if (requiredItems && requiredItems.length > 0) {
return ["heldItem", requiredItems[0].type.name];
@ -939,7 +1042,7 @@ export class LevelRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(requiredLevelRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(requiredLevelRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -960,11 +1063,14 @@ export class LevelRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => pokemon.level >= this.requiredLevelRange[0] && pokemon.level <= this.requiredLevelRange[1]);
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredLevelRanges
return partyPokemon.filter((pokemon) => pokemon.level < this.requiredLevelRange[0] || pokemon.level > this.requiredLevelRange[1]);
return partyPokemon.filter(
pokemon => pokemon.level >= this.requiredLevelRange[0] && pokemon.level <= this.requiredLevelRange[1],
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredLevelRanges
return partyPokemon.filter(
pokemon => pokemon.level < this.requiredLevelRange[0] || pokemon.level > this.requiredLevelRange[1],
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -977,7 +1083,7 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(requiredFriendshipRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(requiredFriendshipRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -986,7 +1092,10 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
override meetsRequirement(): boolean {
// Party Pokemon inside required friendship range
if (!isNullOrUndefined(this.requiredFriendshipRange) && this.requiredFriendshipRange[0] <= this.requiredFriendshipRange[1]) {
if (
!isNullOrUndefined(this.requiredFriendshipRange) &&
this.requiredFriendshipRange[0] <= this.requiredFriendshipRange[1]
) {
const partyPokemon = globalScene.getPlayerParty();
const pokemonInRange = this.queryParty(partyPokemon);
if (pokemonInRange.length < this.minNumberOfPokemon) {
@ -998,11 +1107,17 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => pokemon.friendship >= this.requiredFriendshipRange[0] && pokemon.friendship <= this.requiredFriendshipRange[1]);
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredFriendshipRanges
return partyPokemon.filter((pokemon) => pokemon.friendship < this.requiredFriendshipRange[0] || pokemon.friendship > this.requiredFriendshipRange[1]);
return partyPokemon.filter(
pokemon =>
pokemon.friendship >= this.requiredFriendshipRange[0] &&
pokemon.friendship <= this.requiredFriendshipRange[1],
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredFriendshipRanges
return partyPokemon.filter(
pokemon =>
pokemon.friendship < this.requiredFriendshipRange[0] || pokemon.friendship > this.requiredFriendshipRange[1],
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -1020,7 +1135,7 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(requiredHealthRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -1041,13 +1156,17 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => {
return pokemon.getHpRatio() >= this.requiredHealthRange[0] && pokemon.getHpRatio() <= this.requiredHealthRange[1];
return partyPokemon.filter(pokemon => {
return (
pokemon.getHpRatio() >= this.requiredHealthRange[0] && pokemon.getHpRatio() <= this.requiredHealthRange[1]
);
});
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredHealthRanges
return partyPokemon.filter((pokemon) => pokemon.getHpRatio() < this.requiredHealthRange[0] || pokemon.getHpRatio() > this.requiredHealthRange[1]);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredHealthRanges
return partyPokemon.filter(
pokemon =>
pokemon.getHpRatio() < this.requiredHealthRange[0] || pokemon.getHpRatio() > this.requiredHealthRange[1],
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -1064,7 +1183,7 @@ export class WeightRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(requiredWeightRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
constructor(requiredWeightRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
@ -1085,15 +1204,18 @@ export class WeightRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => pokemon.getWeight() >= this.requiredWeightRange[0] && pokemon.getWeight() <= this.requiredWeightRange[1]);
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredWeightRanges
return partyPokemon.filter((pokemon) => pokemon.getWeight() < this.requiredWeightRange[0] || pokemon.getWeight() > this.requiredWeightRange[1]);
return partyPokemon.filter(
pokemon =>
pokemon.getWeight() >= this.requiredWeightRange[0] && pokemon.getWeight() <= this.requiredWeightRange[1],
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredWeightRanges
return partyPokemon.filter(
pokemon => pokemon.getWeight() < this.requiredWeightRange[0] || pokemon.getWeight() > this.requiredWeightRange[1],
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return ["weight", pokemon?.getWeight().toString() ?? ""];
}
}

View File

@ -12,7 +12,14 @@ import type MysteryEncounterDialogue from "./mystery-encounter-dialogue";
import type { OptionPhaseCallback } from "./mystery-encounter-option";
import type MysteryEncounterOption from "./mystery-encounter-option";
import { MysteryEncounterOptionBuilder } from "./mystery-encounter-option";
import { EncounterPokemonRequirement, EncounterSceneRequirement, HealthRatioRequirement, PartySizeRequirement, StatusEffectRequirement, WaveRangeRequirement } from "./mystery-encounter-requirements";
import {
EncounterPokemonRequirement,
EncounterSceneRequirement,
HealthRatioRequirement,
PartySizeRequirement,
StatusEffectRequirement,
WaveRangeRequirement,
} from "./mystery-encounter-requirements";
import type { BattlerIndex } from "#app/battle";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
@ -278,7 +285,10 @@ export default class MysteryEncounter implements IMysteryEncounter {
this.dialogue = this.dialogue ?? {};
this.spriteConfigs = this.spriteConfigs ? [...this.spriteConfigs] : [];
// Default max is 1 for ROGUE encounters, 2 for others
this.maxAllowedEncounters = this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE ? DEFAULT_MAX_ALLOWED_ROGUE_ENCOUNTERS : DEFAULT_MAX_ALLOWED_ENCOUNTERS;
this.maxAllowedEncounters =
(this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE)
? DEFAULT_MAX_ALLOWED_ROGUE_ENCOUNTERS
: DEFAULT_MAX_ALLOWED_ENCOUNTERS;
this.encounterMode = MysteryEncounterMode.DEFAULT;
this.requirements = this.requirements ? this.requirements : [];
this.hideBattleIntroMessage = this.hideBattleIntroMessage ?? false;
@ -317,7 +327,13 @@ export default class MysteryEncounter implements IMysteryEncounter {
* @param pokemon
*/
pokemonMeetsPrimaryRequirements(pokemon: Pokemon): boolean {
return !this.primaryPokemonRequirements.some(req => !req.queryParty(globalScene.getPlayerParty()).map(p => p.id).includes(pokemon.id));
return !this.primaryPokemonRequirements.some(
req =>
!req
.queryParty(globalScene.getPlayerParty())
.map(p => p.id)
.includes(pokemon.id),
);
}
/**
@ -359,29 +375,28 @@ export default class MysteryEncounter implements IMysteryEncounter {
} else {
overlap.push(qp);
}
}
if (truePrimaryPool.length > 0) {
// Always choose from the non-overlapping pokemon first
this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)];
return true;
} else {
}
// If there are multiple overlapping pokemon, we're okay - just choose one and take it out of the primary pokemon pool
if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) {
if (overlap.length > 1 || this.secondaryPokemon.length - overlap.length >= 1) {
// is this working?
this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)];
this.secondaryPokemon = this.secondaryPokemon.filter((supp) => supp !== this.primaryPokemon);
this.secondaryPokemon = this.secondaryPokemon.filter(supp => supp !== this.primaryPokemon);
return true;
}
console.log("Mystery Encounter Edge Case: Requirement not met due to primary pokemon overlapping with secondary pokemon. There's no valid primary pokemon left.");
console.log(
"Mystery Encounter Edge Case: Requirement not met due to primary pokemon overlapping with secondary pokemon. There's no valid primary pokemon left.",
);
return false;
}
} else {
// this means we CAN have the same pokemon be a primary and secondary pokemon, so just choose any qualifying one randomly.
this.primaryPokemon = qualified[Utils.randSeedInt(qualified.length, 0)];
return true;
}
}
/**
* Returns true if all SECONDARY {@linkcode EncounterRequirement}s for the option are met,
@ -533,28 +548,28 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]];
enemyPartyConfigs: EnemyPartyConfig[] = [];
localizationKey: string = "";
localizationKey = "";
dialogue: MysteryEncounterDialogue = {};
requirements: EncounterSceneRequirement[] = [];
primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
secondaryPokemonRequirements: EncounterPokemonRequirement[] = [];
excludePrimaryFromSupportRequirements: boolean = true;
excludePrimaryFromSupportRequirements = true;
dialogueTokens: Record<string, string> = {};
hideBattleIntroMessage: boolean = false;
autoHideIntroVisuals: boolean = true;
enterIntroVisualsFromRight: boolean = false;
continuousEncounter: boolean = false;
catchAllowed: boolean = false;
fleeAllowed: boolean = true;
lockEncounterRewardTiers: boolean = false;
startOfBattleEffectsComplete: boolean = false;
hasBattleAnimationsWithoutTargets: boolean = false;
skipEnemyBattleTurns: boolean = false;
skipToFightInput: boolean = false;
preventGameStatsUpdates: boolean = false;
maxAllowedEncounters: number = 3;
expMultiplier: number = 1;
hideBattleIntroMessage = false;
autoHideIntroVisuals = true;
enterIntroVisualsFromRight = false;
continuousEncounter = false;
catchAllowed = false;
fleeAllowed = true;
lockEncounterRewardTiers = false;
startOfBattleEffectsComplete = false;
hasBattleAnimationsWithoutTargets = false;
skipEnemyBattleTurns = false;
skipToFightInput = false;
preventGameStatsUpdates = false;
maxAllowedEncounters = 3;
expMultiplier = 1;
/**
* REQUIRED
@ -566,7 +581,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param encounterType
* @returns this
*/
static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & Pick<IMysteryEncounter, "encounterType"> {
static withEncounterType(
encounterType: MysteryEncounterType,
): MysteryEncounterBuilder & Pick<IMysteryEncounter, "encounterType"> {
return Object.assign(new MysteryEncounterBuilder(), { encounterType });
}
@ -582,10 +599,31 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
if (!this.options) {
const options = [option];
return Object.assign(this, { options });
} else {
}
this.options.push(option);
return this;
}
/**
* Defines an option + phasefor the encounter.
* Use for easy/streamlined options.
* There should be at least 2 options defined and no more than 4.
* If complex use {@linkcode MysteryEncounterBuilder.withOption}
*
* @param dialogue {@linkcode OptionTextDisplay}
* @param callback {@linkcode OptionPhaseCallback}
* @returns
*/
withSimpleOption(
dialogue: OptionTextDisplay,
callback: OptionPhaseCallback,
): this & Pick<IMysteryEncounter, "options"> {
return this.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue(dialogue)
.withOptionPhase(callback)
.build(),
);
}
/**
@ -598,26 +636,17 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param callback {@linkcode OptionPhaseCallback}
* @returns
*/
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
return this.withOption(MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build());
}
/**
* Defines an option + phasefor the encounter.
* Use for easy/streamlined options.
* There should be at least 2 options defined and no more than 4.
* If complex use {@linkcode MysteryEncounterBuilder.withOption}
*
* @param dialogue {@linkcode OptionTextDisplay}
* @param callback {@linkcode OptionPhaseCallback}
* @returns
*/
withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
return this.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
withSimpleDexProgressOption(
dialogue: OptionTextDisplay,
callback: OptionPhaseCallback,
): this & Pick<IMysteryEncounter, "options"> {
return this.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue(dialogue)
.withOptionPhase(callback).build());
.withOptionPhase(callback)
.build(),
);
}
/**
@ -626,7 +655,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param spriteConfigs
* @returns
*/
withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick<IMysteryEncounter, "spriteConfigs"> {
withIntroSpriteConfigs(
spriteConfigs: MysteryEncounterSpriteConfig[],
): this & Pick<IMysteryEncounter, "spriteConfigs"> {
return Object.assign(this, { spriteConfigs: spriteConfigs });
}
@ -635,7 +666,13 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
return this;
}
withIntro({ spriteConfigs, dialogue } : {spriteConfigs: MysteryEncounterSpriteConfig[], dialogue?: MysteryEncounterDialogue["intro"]}) {
withIntro({
spriteConfigs,
dialogue,
}: {
spriteConfigs: MysteryEncounterSpriteConfig[];
dialogue?: MysteryEncounterDialogue["intro"];
}) {
return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue);
}
@ -676,7 +713,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param encounterAnimations
* @returns
*/
withAnimations(...encounterAnimations: EncounterAnim[]): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
withAnimations(
...encounterAnimations: EncounterAnim[]
): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
return Object.assign(this, { encounterAnimations: animations });
}
@ -686,7 +725,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @returns
* @param disallowedGameModes
*/
withDisallowedGameModes(...disallowedGameModes: GameModes[]): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
withDisallowedGameModes(
...disallowedGameModes: GameModes[]
): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes];
return Object.assign(this, { disallowedGameModes: gameModes });
}
@ -696,7 +737,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @returns
* @param disallowedChallenges
*/
withDisallowedChallenges(...disallowedChallenges: Challenges[]): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
withDisallowedChallenges(
...disallowedChallenges: Challenges[]
): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges];
return Object.assign(this, { disallowedChallenges: challenges });
}
@ -707,7 +750,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* Default false
* @param continuousEncounter
*/
withContinuousEncounter(continuousEncounter: boolean): this & Required<Pick<IMysteryEncounter, "continuousEncounter">> {
withContinuousEncounter(
continuousEncounter: boolean,
): this & Required<Pick<IMysteryEncounter, "continuousEncounter">> {
return Object.assign(this, { continuousEncounter: continuousEncounter });
}
@ -717,7 +762,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* Default false
* @param hasBattleAnimationsWithoutTargets
*/
withBattleAnimationsWithoutTargets(hasBattleAnimationsWithoutTargets: boolean): this & Required<Pick<IMysteryEncounter, "hasBattleAnimationsWithoutTargets">> {
withBattleAnimationsWithoutTargets(
hasBattleAnimationsWithoutTargets: boolean,
): this & Required<Pick<IMysteryEncounter, "hasBattleAnimationsWithoutTargets">> {
return Object.assign(this, { hasBattleAnimationsWithoutTargets });
}
@ -727,7 +774,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* Default false
* @param skipEnemyBattleTurns
*/
withSkipEnemyBattleTurns(skipEnemyBattleTurns: boolean): this & Required<Pick<IMysteryEncounter, "skipEnemyBattleTurns">> {
withSkipEnemyBattleTurns(
skipEnemyBattleTurns: boolean,
): this & Required<Pick<IMysteryEncounter, "skipEnemyBattleTurns">> {
return Object.assign(this, { skipEnemyBattleTurns });
}
@ -744,7 +793,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* If true, will prevent updating {@linkcode GameStats} for encountering and/or defeating Pokemon
* Default `false`
*/
withPreventGameStatsUpdates(preventGameStatsUpdates: boolean): this & Required<Pick<IMysteryEncounter, "preventGameStatsUpdates">> {
withPreventGameStatsUpdates(
preventGameStatsUpdates: boolean,
): this & Required<Pick<IMysteryEncounter, "preventGameStatsUpdates">> {
return Object.assign(this, { preventGameStatsUpdates });
}
@ -753,7 +804,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param maxAllowedEncounters
* @returns
*/
withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required<Pick<IMysteryEncounter, "maxAllowedEncounters">> {
withMaxAllowedEncounters(
maxAllowedEncounters: number,
): this & Required<Pick<IMysteryEncounter, "maxAllowedEncounters">> {
return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters });
}
@ -764,7 +817,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param requirement
* @returns
*/
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounter, "requirements">> {
withSceneRequirement(
requirement: EncounterSceneRequirement,
): this & Required<Pick<IMysteryEncounter, "requirements">> {
if (requirement instanceof EncounterPokemonRequirement) {
Error("Incorrectly added pokemon requirement as scene requirement.");
}
@ -791,7 +846,11 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param excludeDisallowedPokemon if true, only counts allowed (legal in Challenge/unfainted) mons
* @returns
*/
withScenePartySizeRequirement(min: number, max?: number, excludeDisallowedPokemon: boolean = false): this & Required<Pick<IMysteryEncounter, "requirements">> {
withScenePartySizeRequirement(
min: number,
max?: number,
excludeDisallowedPokemon = false,
): this & Required<Pick<IMysteryEncounter, "requirements">> {
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeDisallowedPokemon));
}
@ -801,13 +860,17 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param requirement {@linkcode EncounterPokemonRequirement}
* @returns
*/
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
withPrimaryPokemonRequirement(
requirement: EncounterPokemonRequirement,
): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
this.primaryPokemonRequirements.push(requirement);
return Object.assign(this, { primaryPokemonRequirements: this.primaryPokemonRequirements });
return Object.assign(this, {
primaryPokemonRequirements: this.primaryPokemonRequirements,
});
}
/**
@ -818,8 +881,14 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param invertQuery if true will invert the query
* @returns
*/
withPrimaryPokemonStatusEffectRequirement(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
return this.withPrimaryPokemonRequirement(new StatusEffectRequirement(statusEffect, minNumberOfPokemon, invertQuery));
withPrimaryPokemonStatusEffectRequirement(
statusEffect: StatusEffect | StatusEffect[],
minNumberOfPokemon = 1,
invertQuery = false,
): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
return this.withPrimaryPokemonRequirement(
new StatusEffectRequirement(statusEffect, minNumberOfPokemon, invertQuery),
);
}
/**
@ -830,21 +899,33 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param invertQuery if true will invert the query
* @returns
*/
withPrimaryPokemonHealthRatioRequirement(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
return this.withPrimaryPokemonRequirement(new HealthRatioRequirement(requiredHealthRange, minNumberOfPokemon, invertQuery));
withPrimaryPokemonHealthRatioRequirement(
requiredHealthRange: [number, number],
minNumberOfPokemon = 1,
invertQuery = false,
): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
return this.withPrimaryPokemonRequirement(
new HealthRatioRequirement(requiredHealthRange, minNumberOfPokemon, invertQuery),
);
}
// TODO: Maybe add an optional parameter for excluding primary pokemon from the support cast?
// ex. if your only grass type pokemon, a snivy, is chosen as primary, if the support pokemon requires a grass type, the event won't trigger because
// it's already been
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = false): this & Required<Pick<IMysteryEncounter, "secondaryPokemonRequirements">> {
withSecondaryPokemonRequirement(
requirement: EncounterPokemonRequirement,
excludePrimaryFromSecondaryRequirements = false,
): this & Required<Pick<IMysteryEncounter, "secondaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
this.secondaryPokemonRequirements.push(requirement);
this.excludePrimaryFromSupportRequirements = excludePrimaryFromSecondaryRequirements;
return Object.assign(this, { excludePrimaryFromSecondaryRequirements: this.excludePrimaryFromSupportRequirements, secondaryPokemonRequirements: this.secondaryPokemonRequirements });
return Object.assign(this, {
excludePrimaryFromSecondaryRequirements: this.excludePrimaryFromSupportRequirements,
secondaryPokemonRequirements: this.secondaryPokemonRequirements,
});
}
/**
@ -919,15 +1000,21 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param hideBattleIntroMessage If `true`, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter
* @returns
*/
withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required<Pick<IMysteryEncounter, "hideBattleIntroMessage">> {
return Object.assign(this, { hideBattleIntroMessage: hideBattleIntroMessage });
withHideWildIntroMessage(
hideBattleIntroMessage: boolean,
): this & Required<Pick<IMysteryEncounter, "hideBattleIntroMessage">> {
return Object.assign(this, {
hideBattleIntroMessage: hideBattleIntroMessage,
});
}
/**
* @param autoHideIntroVisuals If `false`, will not hide the intro visuals that are displayed at the beginning of encounter
* @returns
*/
withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required<Pick<IMysteryEncounter, "autoHideIntroVisuals">> {
withAutoHideIntroVisuals(
autoHideIntroVisuals: boolean,
): this & Required<Pick<IMysteryEncounter, "autoHideIntroVisuals">> {
return Object.assign(this, { autoHideIntroVisuals: autoHideIntroVisuals });
}
@ -936,8 +1023,12 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* Default false
* @returns
*/
withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required<Pick<IMysteryEncounter, "enterIntroVisualsFromRight">> {
return Object.assign(this, { enterIntroVisualsFromRight: enterIntroVisualsFromRight });
withEnterIntroVisualsFromRight(
enterIntroVisualsFromRight: boolean,
): this & Required<Pick<IMysteryEncounter, "enterIntroVisualsFromRight">> {
return Object.assign(this, {
enterIntroVisualsFromRight: enterIntroVisualsFromRight,
});
}
/**
@ -954,7 +1045,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
encounterOptionsDialogue: {
...encounterOptionsDialogue,
title,
}
},
};
return this;
@ -974,7 +1065,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
encounterOptionsDialogue: {
...encounterOptionsDialogue,
description,
}
},
};
return this;
@ -994,7 +1085,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
encounterOptionsDialogue: {
...encounterOptionsDialogue,
query,
}
},
};
return this;

View File

@ -80,7 +80,7 @@ export const EXTREME_ENCOUNTER_BIOMES = [
Biome.WASTELAND,
Biome.ABYSS,
Biome.SPACE,
Biome.END
Biome.END,
];
export const NON_EXTREME_ENCOUNTER_BIOMES = [
@ -108,7 +108,7 @@ export const NON_EXTREME_ENCOUNTER_BIOMES = [
Biome.SLUM,
Biome.SNOWY_FOREST,
Biome.ISLAND,
Biome.LABORATORY
Biome.LABORATORY,
];
/**
@ -147,7 +147,7 @@ export const HUMAN_TRANSITABLE_BIOMES = [
Biome.SLUM,
Biome.SNOWY_FOREST,
Biome.ISLAND,
Biome.LABORATORY
Biome.LABORATORY,
];
/**
@ -168,11 +168,12 @@ export const CIVILIZATION_ENCOUNTER_BIOMES = [
Biome.FACTORY,
Biome.CONSTRUCTION_SITE,
Biome.SLUM,
Biome.ISLAND
Biome.ISLAND,
];
export const allMysteryEncounters: { [encounterType: number]: MysteryEncounter } = {};
export const allMysteryEncounters: {
[encounterType: number]: MysteryEncounter;
} = {};
const extremeBiomeEncounters: MysteryEncounterType[] = [];
@ -187,14 +188,14 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
MysteryEncounterType.THE_POKEMON_SALESMAN,
// MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, Disabled
MysteryEncounterType.THE_WINSTRATE_CHALLENGE,
MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER
MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER,
];
const civilizationBiomeEncounters: MysteryEncounterType[] = [
MysteryEncounterType.DEPARTMENT_STORE_SALE,
MysteryEncounterType.PART_TIMER,
MysteryEncounterType.FUN_AND_GAMES,
MysteryEncounterType.GLOBAL_TRADE_SYSTEM
MysteryEncounterType.GLOBAL_TRADE_SYSTEM,
];
/**
@ -213,7 +214,7 @@ const anyBiomeEncounters: MysteryEncounterType[] = [
MysteryEncounterType.WEIRD_DREAM,
MysteryEncounterType.TELEPORTING_HIJINKS,
MysteryEncounterType.BUG_TYPE_SUPERFAN,
MysteryEncounterType.UNCOMMON_BREED
MysteryEncounterType.UNCOMMON_BREED,
];
/**
@ -225,71 +226,39 @@ const anyBiomeEncounters: MysteryEncounterType[] = [
*/
export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
[Biome.TOWN, []],
[ Biome.PLAINS, [
MysteryEncounterType.SLUMBERING_SNORLAX,
MysteryEncounterType.ABSOLUTE_AVARICE
]],
[ Biome.GRASS, [
MysteryEncounterType.SLUMBERING_SNORLAX,
MysteryEncounterType.ABSOLUTE_AVARICE
]],
[ Biome.TALL_GRASS, [
MysteryEncounterType.ABSOLUTE_AVARICE
]],
[Biome.PLAINS, [MysteryEncounterType.SLUMBERING_SNORLAX, MysteryEncounterType.ABSOLUTE_AVARICE]],
[Biome.GRASS, [MysteryEncounterType.SLUMBERING_SNORLAX, MysteryEncounterType.ABSOLUTE_AVARICE]],
[Biome.TALL_GRASS, [MysteryEncounterType.ABSOLUTE_AVARICE]],
[Biome.METROPOLIS, []],
[ Biome.FOREST, [
MysteryEncounterType.SAFARI_ZONE,
MysteryEncounterType.ABSOLUTE_AVARICE
]],
[ Biome.SEA, [
MysteryEncounterType.LOST_AT_SEA
]],
[ Biome.SWAMP, [
MysteryEncounterType.SAFARI_ZONE
]],
[Biome.FOREST, [MysteryEncounterType.SAFARI_ZONE, MysteryEncounterType.ABSOLUTE_AVARICE]],
[Biome.SEA, [MysteryEncounterType.LOST_AT_SEA]],
[Biome.SWAMP, [MysteryEncounterType.SAFARI_ZONE]],
[Biome.BEACH, []],
[Biome.LAKE, []],
[Biome.SEABED, []],
[Biome.MOUNTAIN, []],
[ Biome.BADLANDS, [
MysteryEncounterType.DANCING_LESSONS
]],
[ Biome.CAVE, [
MysteryEncounterType.THE_STRONG_STUFF
]],
[ Biome.DESERT, [
MysteryEncounterType.DANCING_LESSONS
]],
[Biome.BADLANDS, [MysteryEncounterType.DANCING_LESSONS]],
[Biome.CAVE, [MysteryEncounterType.THE_STRONG_STUFF]],
[Biome.DESERT, [MysteryEncounterType.DANCING_LESSONS]],
[Biome.ICE_CAVE, []],
[Biome.MEADOW, []],
[Biome.POWER_PLANT, []],
[ Biome.VOLCANO, [
MysteryEncounterType.FIERY_FALLOUT,
MysteryEncounterType.DANCING_LESSONS
]],
[Biome.VOLCANO, [MysteryEncounterType.FIERY_FALLOUT, MysteryEncounterType.DANCING_LESSONS]],
[Biome.GRAVEYARD, []],
[Biome.DOJO, []],
[Biome.FACTORY, []],
[Biome.RUINS, []],
[ Biome.WASTELAND, [
MysteryEncounterType.DANCING_LESSONS
]],
[ Biome.ABYSS, [
MysteryEncounterType.DANCING_LESSONS
]],
[ Biome.SPACE, [
MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER
]],
[Biome.WASTELAND, [MysteryEncounterType.DANCING_LESSONS]],
[Biome.ABYSS, [MysteryEncounterType.DANCING_LESSONS]],
[Biome.SPACE, [MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER]],
[Biome.CONSTRUCTION_SITE, []],
[ Biome.JUNGLE, [
MysteryEncounterType.SAFARI_ZONE
]],
[Biome.JUNGLE, [MysteryEncounterType.SAFARI_ZONE]],
[Biome.FAIRY_CAVE, []],
[Biome.TEMPLE, []],
[Biome.SLUM, []],
[Biome.SNOWY_FOREST, []],
[Biome.ISLAND, []],
[ Biome.LABORATORY, []]
[Biome.LABORATORY, []],
]);
export function initMysteryEncounters() {
@ -364,7 +333,7 @@ export function initMysteryEncounters() {
// Add ANY biome encounters to biome map
// eslint-disable-next-line
let encounterBiomeTableLog = "";
let _encounterBiomeTableLog = "";
mysteryEncountersByBiome.forEach((biomeEncounters, biome) => {
anyBiomeEncounters.forEach(encounter => {
if (!biomeEncounters.includes(encounter)) {
@ -372,7 +341,10 @@ export function initMysteryEncounters() {
}
});
encounterBiomeTableLog += `${getBiomeName(biome).toUpperCase()}: [${biomeEncounters.map(type => MysteryEncounterType[type].toString().toLowerCase()).sort().join(", ")}]\n`;
_encounterBiomeTableLog += `${getBiomeName(biome).toUpperCase()}: [${biomeEncounters
.map(type => MysteryEncounterType[type].toString().toLowerCase())
.sort()
.join(", ")}]\n`;
});
//console.debug("All Mystery Encounters by Biome:\n" + encounterBiomeTableLog);

View File

@ -40,7 +40,9 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
}
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty().filter((pkm) => (this.includeFainted ? pkm.isAllowedInChallenge() : pkm.isAllowedInBattle()));
const partyPokemon = globalScene
.getPlayerParty()
.filter(pkm => (this.includeFainted ? pkm.isAllowedInChallenge() : pkm.isAllowedInBattle()));
if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
return false;
@ -51,17 +53,16 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) =>
return partyPokemon.filter(pokemon =>
// every required move should be included
this.requiredMoves.every((requiredMove) => this.getAllPokemonMoves(pokemon).includes(requiredMove))
);
} else {
return partyPokemon.filter(
(pokemon) =>
// none of the "required" moves should be included
!this.requiredMoves.some((requiredMove) => this.getAllPokemonMoves(pokemon).includes(requiredMove))
this.requiredMoves.every(requiredMove => this.getAllPokemonMoves(pokemon).includes(requiredMove)),
);
}
return partyPokemon.filter(
pokemon =>
// none of the "required" moves should be included
!this.requiredMoves.some(requiredMove => this.getAllPokemonMoves(pokemon).includes(requiredMove)),
);
}
override getDialogueToken(__pokemon?: PlayerPokemon): [string, string] {

View File

@ -4,14 +4,7 @@ import { Abilities } from "#enums/abilities";
/**
* Moves that "steal" things
*/
export const STEALING_MOVES = [
Moves.PLUCK,
Moves.COVET,
Moves.KNOCK_OFF,
Moves.THIEF,
Moves.TRICK,
Moves.SWITCHEROO
];
export const STEALING_MOVES = [Moves.PLUCK, Moves.COVET, Moves.KNOCK_OFF, Moves.THIEF, Moves.TRICK, Moves.SWITCHEROO];
/**
* Moves that "charm" someone
@ -24,7 +17,7 @@ export const CHARMING_MOVES = [
Moves.ATTRACT,
Moves.SWEET_SCENT,
Moves.CAPTIVATE,
Moves.AROMATIC_MIST
Moves.AROMATIC_MIST,
];
/**
@ -42,7 +35,7 @@ export const DANCING_MOVES = [
Moves.QUIVER_DANCE,
Moves.SWORDS_DANCE,
Moves.TEETER_DANCE,
Moves.VICTORY_DANCE
Moves.VICTORY_DANCE,
];
/**
@ -60,7 +53,7 @@ export const DISTRACTION_MOVES = [
Moves.CAPTIVATE,
Moves.RAGE_POWDER,
Moves.SUBSTITUTE,
Moves.SHED_TAIL
Moves.SHED_TAIL,
];
/**
@ -79,7 +72,7 @@ export const PROTECTING_MOVES = [
Moves.CRAFTY_SHIELD,
Moves.SPIKY_SHIELD,
Moves.OBSTRUCT,
Moves.DETECT
Moves.DETECT,
];
/**
@ -116,7 +109,7 @@ export const EXTORTION_ABILITIES = [
Abilities.ARENA_TRAP,
Abilities.SHADOW_TAG,
Abilities.SUCTION_CUPS,
Abilities.STICKY_HOLD
Abilities.STICKY_HOLD,
];
/**
@ -133,5 +126,5 @@ export const FIRE_RESISTANT_ABILITIES = [
Abilities.MAGMA_ARMOR,
Abilities.WATER_VEIL,
Abilities.STEAM_ENGINE,
Abilities.PRIMORDIAL_SEA
Abilities.PRIMORDIAL_SEA,
];

View File

@ -63,7 +63,13 @@ export function queueEncounterMessage(contentKey: string): void {
* @param callbackDelay
* @param promptDelay
*/
export function showEncounterText(contentKey: string, delay: number | null = null, callbackDelay: number = 0, prompt: boolean = true, promptDelay: number | null = null): Promise<void> {
export function showEncounterText(
contentKey: string,
delay: number | null = null,
callbackDelay = 0,
prompt = true,
promptDelay: number | null = null,
): Promise<void> {
return new Promise<void>(resolve => {
const text: string | null = getEncounterText(contentKey);
globalScene.ui.showText(text ?? "", delay, () => resolve(), callbackDelay, prompt, promptDelay);
@ -78,7 +84,12 @@ export function showEncounterText(contentKey: string, delay: number | null = nul
* @param speakerContentKey
* @param callbackDelay
*/
export function showEncounterDialogue(textContentKey: string, speakerContentKey: string, delay: number | null = null, callbackDelay: number = 0): Promise<void> {
export function showEncounterDialogue(
textContentKey: string,
speakerContentKey: string,
delay: number | null = null,
callbackDelay = 0,
): Promise<void> {
return new Promise<void>(resolve => {
const text: string | null = getEncounterText(textContentKey);
const speaker: string | null = getEncounterText(speakerContentKey);

View File

@ -2,14 +2,29 @@ import type Battle from "#app/battle";
import { BattlerIndex, BattleType } from "#app/battle";
import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes";
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
import {
AVERAGE_ENCOUNTERS_PER_RUN_TARGET,
WEIGHT_INCREMENT_ON_SPAWN_MISS,
} from "#app/data/mystery-encounters/mystery-encounters";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import type { AiType, PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, FieldPosition, PokemonMove, PokemonSummonData } from "#app/field/pokemon";
import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type";
import { getPartyLuckValue, ModifierPoolType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases";
import {
getPartyLuckValue,
ModifierPoolType,
ModifierTypeGenerator,
ModifierTypeOption,
modifierTypes,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import {
MysteryEncounterBattlePhase,
MysteryEncounterBattleStartCleanupPhase,
MysteryEncounterPhase,
MysteryEncounterRewardsPhase,
} from "#app/phases/mystery-encounter-phases";
import type PokemonData from "#app/system/pokemon-data";
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
@ -46,7 +61,7 @@ import type { Variant } from "#app/data/variant";
import { StatusEffect } from "#enums/status-effect";
import { globalScene } from "#app/global-scene";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Type } from "#app/enums/type";
import { PokemonType } from "#enums/pokemon-type";
import { getNatureName } from "#app/data/nature";
import { getPokemonNameWithAffix } from "#app/messages";
@ -71,7 +86,7 @@ export function doTrainerExclamation() {
globalScene.time.delayedCall(800, () => {
globalScene.field.remove(exclamationSprite, true);
});
}
},
});
globalScene.playSound("battle_anims/GEN8- Exclaim", { volume: 0.7 });
@ -101,7 +116,7 @@ export interface EnemyPokemonConfig {
modifierConfigs?: HeldModifierConfig[];
tags?: BattlerTagType[];
dataSource?: PokemonData;
tera?: Type;
tera?: PokemonType;
aiType?: AiType;
}
@ -151,8 +166,15 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle);
doubleBattle = doubleTrainer;
const trainerFemale = isNullOrUndefined(partyConfig.female) ? !!(Utils.randSeedInt(2)) : partyConfig.female;
const newTrainer = new Trainer(trainerConfig.trainerType, doubleTrainer ? TrainerVariant.DOUBLE : trainerFemale ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, undefined, undefined, undefined, trainerConfig);
const trainerFemale = isNullOrUndefined(partyConfig.female) ? !!Utils.randSeedInt(2) : partyConfig.female;
const newTrainer = new Trainer(
trainerConfig.trainerType,
doubleTrainer ? TrainerVariant.DOUBLE : trainerFemale ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
undefined,
undefined,
undefined,
trainerConfig,
);
newTrainer.x += 300;
newTrainer.setVisible(false);
globalScene.field.add(newTrainer);
@ -163,7 +185,12 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
} else {
// Wild
globalScene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.WILD_BATTLE;
const numEnemies = partyConfig?.pokemonConfigs && partyConfig.pokemonConfigs.length > 0 ? partyConfig?.pokemonConfigs?.length : doubleBattle ? 2 : 1;
const numEnemies =
partyConfig?.pokemonConfigs && partyConfig.pokemonConfigs.length > 0
? partyConfig?.pokemonConfigs?.length
: doubleBattle
? 2
: 1;
battle.enemyLevels = new Array(numEnemies).fill(null).map(() => globalScene.currentBattle.getLevelForWave());
}
@ -183,8 +210,8 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
battle.enemyLevels = battle.enemyLevels.map(level => level + additive);
battle.enemyLevels.forEach((level, e) => {
let enemySpecies;
let dataSource;
let enemySpecies: PokemonSpecies | undefined;
let dataSource: PokemonData | undefined;
let isBoss = false;
if (!loaded) {
if ((!isNullOrUndefined(trainerType) || trainerConfig) && battle.trainer) {
@ -195,7 +222,14 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
dataSource = config.dataSource;
enemySpecies = config.species;
isBoss = config.isBoss;
battle.enemyParty[e] = globalScene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, false, dataSource);
battle.enemyParty[e] = globalScene.addEnemyPokemon(
enemySpecies,
level,
TrainerSlot.TRAINER,
isBoss,
false,
dataSource,
);
} else {
battle.enemyParty[e] = battle.trainer.genPartyMember(e);
}
@ -213,7 +247,14 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
enemySpecies = globalScene.randomSpecies(battle.waveIndex, level, true);
}
battle.enemyParty[e] = globalScene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, false, dataSource);
battle.enemyParty[e] = globalScene.addEnemyPokemon(
enemySpecies,
level,
TrainerSlot.NONE,
isBoss,
false,
dataSource,
);
}
}
@ -229,7 +270,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
enemyPokemon.resetSummonData();
}
if (!loaded && isNullOrUndefined(partyConfig.countAsSeen) || partyConfig.countAsSeen) {
if ((!loaded && isNullOrUndefined(partyConfig.countAsSeen)) || partyConfig.countAsSeen) {
globalScene.gameData.setPokemonSeen(enemyPokemon, true, !!(trainerType || trainerConfig));
}
@ -268,7 +309,9 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
// Set Boss
if (config.isBoss) {
let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments! : globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, level, enemySpecies, true);
let segments = !isNullOrUndefined(config.bossSegments)
? config.bossSegments!
: globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, level, enemySpecies, true);
if (!isNullOrUndefined(config.bossSegmentModifier)) {
segments += config.bossSegmentModifier;
}
@ -295,7 +338,11 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
if (statusEffects) {
// Default to cureturn 3 for sleep
const status = Array.isArray(statusEffects) ? statusEffects[0] : statusEffects;
const cureTurn = Array.isArray(statusEffects) ? statusEffects[1] : statusEffects === StatusEffect.SLEEP ? 3 : undefined;
const cureTurn = Array.isArray(statusEffects)
? statusEffects[1]
: statusEffects === StatusEffect.SLEEP
? 3
: undefined;
enemyPokemon.status = new Status(status, 0, cureTurn);
}
@ -334,7 +381,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
}
// Set tera
if (config.tera && config.tera !== Type.UNKNOWN) {
if (config.tera && config.tera !== PokemonType.UNKNOWN) {
enemyPokemon.teraType = config.tera;
if (battle.trainer) {
battle.trainer.config.setInstantTera(e);
@ -368,7 +415,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
` Spd: ${enemyPokemon.stats[5]} (${enemyPokemon.ivs[5]})`,
];
const moveset: string[] = [];
enemyPokemon.getMoveset().forEach((move) => {
enemyPokemon.getMoveset().forEach(move => {
moveset.push(move!.getName()); // TODO: remove `!` after moveset-null removal PR
});
@ -381,7 +428,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
console.log(
`Ability: ${enemyPokemon.getAbility().name}`,
`| Passive Ability${enemyPokemon.hasPassive() ? "" : " (inactive)"}: ${enemyPokemon.getPassiveAbility().name}`,
`${enemyPokemon.isBoss() ? `| Boss Bars: ${enemyPokemon.bossSegments}` : ""}`
`${enemyPokemon.isBoss() ? `| Boss Bars: ${enemyPokemon.bossSegments}` : ""}`,
);
console.log("Moveset:", moveset);
});
@ -400,7 +447,10 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
}
});
if (!loaded) {
regenerateModifierPoolThresholds(globalScene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD);
regenerateModifierPoolThresholds(
globalScene.getEnemyField(),
battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD,
);
const customModifierTypes = partyConfig?.pokemonConfigs
?.filter(config => config?.modifierConfigs)
.map(config => config.modifierConfigs!);
@ -417,8 +467,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
*/
export function loadCustomMovesForEncounter(moves: Moves | Moves[]) {
moves = Array.isArray(moves) ? moves : [moves];
return Promise.all(moves.map(move => initMoveAnim(move)))
.then(() => loadMoveAnimAssets(moves));
return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves));
}
/**
@ -427,7 +476,7 @@ export function loadCustomMovesForEncounter(moves: Moves | Moves[]) {
* @param playSound
* @param showMessage
*/
export function updatePlayerMoney(changeValue: number, playSound: boolean = true, showMessage: boolean = true) {
export function updatePlayerMoney(changeValue: number, playSound = true, showMessage = true) {
globalScene.money = Math.min(Math.max(globalScene.money + changeValue, 0), Number.MAX_SAFE_INTEGER);
globalScene.updateMoneyText();
globalScene.animateMoneyChanged(false);
@ -436,9 +485,21 @@ export function updatePlayerMoney(changeValue: number, playSound: boolean = true
}
if (showMessage) {
if (changeValue < 0) {
globalScene.queueMessage(i18next.t("mysteryEncounterMessages:paid_money", { amount: -changeValue }), null, true);
globalScene.queueMessage(
i18next.t("mysteryEncounterMessages:paid_money", {
amount: -changeValue,
}),
null,
true,
);
} else {
globalScene.queueMessage(i18next.t("mysteryEncounterMessages:receive_money", { amount: changeValue }), null, true);
globalScene.queueMessage(
i18next.t("mysteryEncounterMessages:receive_money", {
amount: changeValue,
}),
null,
true,
);
}
}
}
@ -461,7 +522,9 @@ export function generateModifierType(modifier: () => ModifierType, pregenArgs?:
.withIdFromFunc(modifierTypes[modifierId])
.withTierFromPool(ModifierPoolType.PLAYER, globalScene.getPlayerParty());
return result instanceof ModifierTypeGenerator ? result.generateType(globalScene.getPlayerParty(), pregenArgs) : result;
return result instanceof ModifierTypeGenerator
? result.generateType(globalScene.getPlayerParty(), pregenArgs)
: result;
}
/**
@ -469,7 +532,10 @@ export function generateModifierType(modifier: () => ModifierType, pregenArgs?:
* @param modifier
* @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
*/
export function generateModifierTypeOption(modifier: () => ModifierType, pregenArgs?: any[]): ModifierTypeOption | null {
export function generateModifierTypeOption(
modifier: () => ModifierType,
pregenArgs?: any[],
): ModifierTypeOption | null {
const result = generateModifierType(modifier, pregenArgs);
if (result) {
return new ModifierTypeOption(result, 0);
@ -484,18 +550,30 @@ export function generateModifierTypeOption(modifier: () => ModifierType, pregenA
* @param onPokemonNotSelected - Any logic that needs to be performed if no Pokemon is chosen
* @param selectablePokemonFilter
*/
export function selectPokemonForOption(onPokemonSelected: (pokemon: PlayerPokemon) => void | OptionSelectItem[], onPokemonNotSelected?: () => void, selectablePokemonFilter?: PokemonSelectFilter): Promise<boolean> {
export function selectPokemonForOption(
// biome-ignore lint/suspicious/noConfusingVoidType: Takes a function that either returns void or an array of OptionSelectItem
onPokemonSelected: (pokemon: PlayerPokemon) => void | OptionSelectItem[],
onPokemonNotSelected?: () => void,
selectablePokemonFilter?: PokemonSelectFilter,
): Promise<boolean> {
return new Promise(resolve => {
const modeToSetOnExit = globalScene.ui.getMode();
// Open party screen to choose pokemon
globalScene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: number, option: PartyOption) => {
globalScene.ui.setMode(
Mode.PARTY,
PartyUiMode.SELECT,
-1,
(slotIndex: number, _option: PartyOption) => {
if (slotIndex < globalScene.getPlayerParty().length) {
globalScene.ui.setMode(modeToSetOnExit).then(() => {
const pokemon = globalScene.getPlayerParty()[slotIndex];
const secondaryOptions = onPokemonSelected(pokemon);
if (!secondaryOptions) {
globalScene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
globalScene.currentBattle.mysteryEncounter!.setDialogueToken(
"selectedPokemon",
pokemon.getNameToRender(),
);
resolve(true);
return;
}
@ -504,17 +582,22 @@ export function selectPokemonForOption(onPokemonSelected: (pokemon: PlayerPokemo
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
const displayOptions = () => {
// Always appends a cancel option to bottom of options
const fullOptions = secondaryOptions.map(option => {
const fullOptions = secondaryOptions
.map(option => {
// Update handler to resolve promise
const onSelect = option.handler;
option.handler = () => {
onSelect();
globalScene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
globalScene.currentBattle.mysteryEncounter!.setDialogueToken(
"selectedPokemon",
pokemon.getNameToRender(),
);
resolve(true);
return true;
};
return option;
}).concat({
})
.concat({
label: i18next.t("menu:cancel"),
handler: () => {
globalScene.ui.clearText();
@ -524,14 +607,14 @@ export function selectPokemonForOption(onPokemonSelected: (pokemon: PlayerPokemo
},
onHover: () => {
showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false);
}
},
});
const config: OptionSelectConfig = {
options: fullOptions,
maxOptions: 7,
yOffset: 0,
supportHover: true
supportHover: true,
};
// Do hover over the starting selection option
@ -541,7 +624,8 @@ export function selectPokemonForOption(onPokemonSelected: (pokemon: PlayerPokemo
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
};
const textPromptKey = globalScene.currentBattle.mysteryEncounter?.selectedOption?.dialogue?.secondOptionPrompt;
const textPromptKey =
globalScene.currentBattle.mysteryEncounter?.selectedOption?.dialogue?.secondOptionPrompt;
if (!textPromptKey) {
displayOptions();
} else {
@ -557,7 +641,9 @@ export function selectPokemonForOption(onPokemonSelected: (pokemon: PlayerPokemo
resolve(false);
});
}
}, selectablePokemonFilter);
},
selectablePokemonFilter,
);
});
}
@ -575,7 +661,12 @@ interface PokemonAndOptionSelected {
* @param selectablePokemonFilter
* @param onHoverOverCancelOption
*/
export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelectPromptKey: string, selectablePokemonFilter?: PokemonSelectFilter, onHoverOverCancelOption?: () => void): Promise<PokemonAndOptionSelected | null> {
export function selectOptionThenPokemon(
options: OptionSelectItem[],
optionSelectPromptKey: string,
selectablePokemonFilter?: PokemonSelectFilter,
onHoverOverCancelOption?: () => void,
): Promise<PokemonAndOptionSelected | null> {
return new Promise<PokemonAndOptionSelected | null>(resolve => {
const modeToSetOnExit = globalScene.ui.getMode();
@ -601,22 +692,32 @@ export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelec
const selectPokemonAfterOption = (selectedOptionIndex: number) => {
// Open party screen to choose a Pokemon
globalScene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: number, option: PartyOption) => {
globalScene.ui.setMode(
Mode.PARTY,
PartyUiMode.SELECT,
-1,
(slotIndex: number, _option: PartyOption) => {
if (slotIndex < globalScene.getPlayerParty().length) {
// Pokemon and option selected
globalScene.ui.setMode(modeToSetOnExit).then(() => {
const result: PokemonAndOptionSelected = { selectedPokemonIndex: slotIndex, selectedOptionIndex: selectedOptionIndex };
const result: PokemonAndOptionSelected = {
selectedPokemonIndex: slotIndex,
selectedOptionIndex: selectedOptionIndex,
};
resolve(result);
});
} else {
// Back to first option select screen
displayOptions(config);
}
}, selectablePokemonFilter);
},
selectablePokemonFilter,
);
};
// Always appends a cancel option to bottom of options
const fullOptions = options.map((option, index) => {
const fullOptions = options
.map((option, index) => {
// Update handler to resolve promise
const onSelect = option.handler;
option.handler = () => {
@ -625,7 +726,8 @@ export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelec
return true;
};
return option;
}).concat({
})
.concat({
label: i18next.t("menu:cancel"),
handler: () => {
globalScene.ui.clearText();
@ -638,14 +740,14 @@ export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelec
onHoverOverCancelOption();
}
showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false);
}
},
});
const config: OptionSelectConfig = {
options: fullOptions,
maxOptions: 7,
yOffset: 0,
supportHover: true
supportHover: true,
};
displayOptions(config);
@ -659,7 +761,11 @@ export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelec
* @param eggRewards
* @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before {@linkcode MysteryEncounterRewardsPhase})
*/
export function setEncounterRewards(customShopRewards?: CustomModifierSettings, eggRewards?: IEggOptions[], preRewardsCallback?: Function) {
export function setEncounterRewards(
customShopRewards?: CustomModifierSettings,
eggRewards?: IEggOptions[],
preRewardsCallback?: Function,
) {
globalScene.currentBattle.mysteryEncounter!.doEncounterRewards = () => {
if (preRewardsCallback) {
preRewardsCallback();
@ -702,7 +808,7 @@ export function setEncounterRewards(customShopRewards?: CustomModifierSettings,
* https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_effort_value_yield_(Generation_IX)
* @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue
*/
export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex: boolean = true) {
export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex = true) {
const participantIds = Array.isArray(participantId) ? participantId : [participantId];
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => {
@ -737,7 +843,10 @@ export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSet
* @param addHealPhase - when true, will add a shop phase to end of encounter with 0 rewards but healing items are available
* @param encounterMode - Can set custom encounter mode if necessary (may be required for forcing Pokemon to return before next phase)
*/
export function leaveEncounterWithoutBattle(addHealPhase: boolean = false, encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE) {
export function leaveEncounterWithoutBattle(
addHealPhase = false,
encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE,
) {
globalScene.currentBattle.mysteryEncounter!.encounterMode = encounterMode;
globalScene.clearPhaseQueue();
globalScene.clearPhaseQueueSplice();
@ -749,8 +858,8 @@ export function leaveEncounterWithoutBattle(addHealPhase: boolean = false, encou
* @param addHealPhase - Adds an empty shop phase to allow player to purchase healing items
* @param doNotContinue - default `false`. If set to true, will not end the battle and continue to next wave
*/
export function handleMysteryEncounterVictory(addHealPhase: boolean = false, doNotContinue: boolean = false) {
const allowedPkm = globalScene.getPlayerParty().filter((pkm) => pkm.isAllowedInBattle());
export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinue = false) {
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
if (allowedPkm.length === 0) {
globalScene.clearPhaseQueue();
@ -763,10 +872,17 @@ export function handleMysteryEncounterVictory(addHealPhase: boolean = false, doN
const encounter = globalScene.currentBattle.mysteryEncounter!;
if (encounter.continuousEncounter || doNotContinue) {
return;
} else if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
}
if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase));
globalScene.pushPhase(new EggLapsePhase());
} else if (!globalScene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
} else if (
!globalScene
.getEnemyParty()
.find(p =>
encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true),
)
) {
globalScene.pushPhase(new BattleEndPhase(true));
if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
globalScene.pushPhase(new TrainerVictoryPhase());
@ -785,8 +901,8 @@ export function handleMysteryEncounterVictory(addHealPhase: boolean = false, doN
* Similar to {@linkcode handleMysteryEncounterVictory}, but for cases where the player lost a battle or failed a challenge
* @param addHealPhase
*/
export function handleMysteryEncounterBattleFailed(addHealPhase: boolean = false, doNotContinue: boolean = false) {
const allowedPkm = globalScene.getPlayerParty().filter((pkm) => pkm.isAllowedInBattle());
export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotContinue = false) {
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
if (allowedPkm.length === 0) {
globalScene.clearPhaseQueue();
@ -799,7 +915,8 @@ export function handleMysteryEncounterBattleFailed(addHealPhase: boolean = false
const encounter = globalScene.currentBattle.mysteryEncounter!;
if (encounter.continuousEncounter || doNotContinue) {
return;
} else if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
}
if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
globalScene.pushPhase(new BattleEndPhase(false));
}
@ -817,7 +934,7 @@ export function handleMysteryEncounterBattleFailed(addHealPhase: boolean = false
* @param destroy - If true, will destroy visuals ONLY ON HIDE TRANSITION. Does nothing on show. Defaults to true
* @param duration
*/
export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, destroy: boolean = true, duration: number = 750): Promise<boolean> {
export function transitionMysteryEncounterIntroVisuals(hide = true, destroy = true, duration = 750): Promise<boolean> {
return new Promise(resolve => {
const introVisuals = globalScene.currentBattle.mysteryEncounter!.introVisuals;
const enemyPokemon = globalScene.getEnemyField();
@ -852,7 +969,7 @@ export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, des
globalScene.currentBattle.mysteryEncounter!.introVisuals = undefined;
}
resolve(true);
}
},
});
} else {
resolve(true);
@ -866,10 +983,15 @@ export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, des
*/
export function handleMysteryEncounterBattleStartEffects() {
const encounter = globalScene.currentBattle.mysteryEncounter;
if (globalScene.currentBattle.isBattleMysteryEncounter() && encounter && encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE && !encounter.startOfBattleEffectsComplete) {
if (
globalScene.currentBattle.isBattleMysteryEncounter() &&
encounter &&
encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE &&
!encounter.startOfBattleEffectsComplete
) {
const effects = encounter.startOfBattleEffects;
effects.forEach(effect => {
let source;
let source: EnemyPokemon | Pokemon;
if (effect.sourcePokemon) {
source = effect.sourcePokemon;
} else if (!isNullOrUndefined(effect.sourceBattlerIndex)) {
@ -887,6 +1009,7 @@ export function handleMysteryEncounterBattleStartEffects() {
} else {
source = globalScene.getEnemyField()[0];
}
// @ts-ignore: source cannot be undefined
globalScene.pushPhase(new MovePhase(source, effect.targets, effect.move, effect.followUp, effect.ignorePp));
});
@ -919,20 +1042,31 @@ export function handleMysteryEncounterTurnStartEffects(): boolean {
* @param rerollHidden whether the mon should get an extra roll for Hidden Ability
* @returns {@linkcode EnemyPokemon} for the requested encounter
*/
export function getRandomEncounterSpecies(level: number, isBoss: boolean = false, rerollHidden: boolean = false): EnemyPokemon {
export function getRandomEncounterSpecies(level: number, isBoss = false, rerollHidden = false): EnemyPokemon {
let bossSpecies: PokemonSpecies;
let isEventEncounter = false;
const eventEncounters = globalScene.eventManager.getEventEncounters();
let formIndex;
let formIndex: number | undefined;
if (eventEncounters.length > 0 && randSeedInt(2) === 1) {
const eventEncounter = randSeedItem(eventEncounters);
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, !eventEncounter.blockEvolution, isBoss, globalScene.gameMode);
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(
level,
!eventEncounter.blockEvolution,
isBoss,
globalScene.gameMode,
);
isEventEncounter = true;
bossSpecies = getPokemonSpecies(levelSpecies);
formIndex = eventEncounter.formIndex;
} else {
bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), isBoss);
bossSpecies = globalScene.arena.randomSpecies(
globalScene.currentBattle.waveIndex,
level,
0,
getPartyLuckValue(globalScene.getPlayerParty()),
isBoss,
);
}
const ret = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, isBoss);
if (formIndex) {
@ -959,8 +1093,17 @@ export function getRandomEncounterSpecies(level: number, isBoss: boolean = false
export function calculateMEAggregateStats(baseSpawnWeight: number) {
const numRuns = 1000;
let run = 0;
const biomes = Object.keys(Biome).filter(key => isNaN(Number(key)));
const alwaysPickTheseBiomes = [ Biome.ISLAND, Biome.ABYSS, Biome.WASTELAND, Biome.FAIRY_CAVE, Biome.TEMPLE, Biome.LABORATORY, Biome.SPACE, Biome.WASTELAND ];
const biomes = Object.keys(Biome).filter(key => Number.isNaN(Number(key)));
const alwaysPickTheseBiomes = [
Biome.ISLAND,
Biome.ABYSS,
Biome.WASTELAND,
Biome.FAIRY_CAVE,
Biome.TEMPLE,
Biome.LABORATORY,
Biome.SPACE,
Biome.WASTELAND,
];
const calculateNumEncounters = (): any[] => {
let encounterRate = baseSpawnWeight; // BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
@ -987,7 +1130,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
.filter(b => {
return !Array.isArray(b) || !Utils.randSeedInt(b[1]);
})
.map(b => !Array.isArray(b) ? b : b[0]);
.map(b => (!Array.isArray(b) ? b : b[0]));
}, i * 100);
if (biomes! && biomes.length > 0) {
const specialBiomes = biomes.filter(b => alwaysPickTheseBiomes.includes(b));
@ -998,7 +1141,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
}
}
} else if (biomeLinks.hasOwnProperty(currentBiome)) {
currentBiome = (biomeLinks[currentBiome] as Biome);
currentBiome = biomeLinks[currentBiome] as Biome;
} else {
if (!(i % 50)) {
currentBiome = Biome.END;
@ -1027,12 +1170,12 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
// If total number of encounters is lower than expected for the run, slightly favor a new encounter
// Do the reverse as well
const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (180 - 10) * (i - 10);
const expectedEncountersByFloor = (AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (180 - 10)) * (i - 10);
const currentRunDiffFromAvg = expectedEncountersByFloor - numEncounters.reduce((a, b) => a + b);
const favoredEncounterRate = encounterRate + currentRunDiffFromAvg * 15;
// If the most recent ME was 3 or fewer waves ago, can never spawn a ME
const canSpawn = (i - mostRecentEncounterWave) > 3;
const canSpawn = i - mostRecentEncounterWave > 3;
if (canSpawn && roll < favoredEncounterRate) {
mostRecentEncounterWave = i;
@ -1052,7 +1195,13 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
const uncommonThreshold = totalWeight - tierWeights[0] - tierWeights[1]; // 64 - 32 - 16 = 16
const rareThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2]; // 64 - 32 - 16 - 10 = 6
tierValue > commonThreshold ? ++numEncounters[0] : tierValue > uncommonThreshold ? ++numEncounters[1] : tierValue > rareThreshold ? ++numEncounters[2] : ++numEncounters[3];
tierValue > commonThreshold
? ++numEncounters[0]
: tierValue > uncommonThreshold
? ++numEncounters[1]
: tierValue > rareThreshold
? ++numEncounters[2]
: ++numEncounters[3];
encountersByBiome.set(Biome[currentBiome], (encountersByBiome.get(Biome[currentBiome]) ?? 0) + 1);
} else {
encounterRate += WEIGHT_INCREMENT_ON_SPAWN_MISS;
@ -1108,13 +1257,17 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
let stats = `Starting weight: ${baseSpawnWeight}\nAverage MEs per run: ${totalMean}\nStandard Deviation: ${totalStd}\nAvg Commons: ${commonMean}\nAvg Greats: ${uncommonMean}\nAvg Ultras: ${rareMean}\nAvg Rogues: ${superRareMean}\n`;
const meanEncountersPerRunPerBiomeSorted = [ ...meanEncountersPerRunPerBiome.entries() ].sort((e1, e2) => e2[1] - e1[1]);
meanEncountersPerRunPerBiomeSorted.forEach(value => stats = stats + `${value[0]}: avg valid floors ${meanMEFloorsPerRunPerBiome.get(value[0])}, avg MEs ${value[1]},\n`);
const meanEncountersPerRunPerBiomeSorted = [...meanEncountersPerRunPerBiome.entries()].sort(
(e1, e2) => e2[1] - e1[1],
);
for (const value of meanEncountersPerRunPerBiomeSorted) {
stats += value[0] + "avg valid floors " + meanMEFloorsPerRunPerBiome.get(value[0]) + ", avg MEs ${value[1]},\n";
}
console.log(stats);
}
/**
* TODO: remove once encounter spawn rate is finalized
* Just a helper function to calculate aggregate stats for MEs in a Classic run
@ -1133,17 +1286,20 @@ export function calculateRareSpawnAggregateStats(luckValue: number) {
// Roll boss tier
// luck influences encounter rarity
let luckModifier = 0;
if (!isNaN(luckValue)) {
if (!Number.isNaN(luckValue)) {
luckModifier = luckValue * 0.5;
}
const tierValue = Utils.randSeedInt(64 - luckModifier);
const tier = tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE;
const tier =
tierValue >= 20
? BiomePoolTier.BOSS
: tierValue >= 6
? BiomePoolTier.BOSS_RARE
: tierValue >= 1
? BiomePoolTier.BOSS_SUPER_RARE
: BiomePoolTier.BOSS_ULTRA_RARE;
switch (tier) {
default:
case BiomePoolTier.BOSS:
++bossEncountersByRarity[0];
break;
case BiomePoolTier.BOSS_RARE:
++bossEncountersByRarity[1];
break;
@ -1153,6 +1309,10 @@ export function calculateRareSpawnAggregateStats(luckValue: number) {
case BiomePoolTier.BOSS_ULTRA_RARE:
++bossEncountersByRarity[3];
break;
case BiomePoolTier.BOSS:
default:
++bossEncountersByRarity[0];
break;
}
}

View File

@ -4,7 +4,12 @@ import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor } from "#app/data/pokeball";
import {
doPokeballBounceAnim,
getPokeballAtlasKey,
getPokeballCatchMultiplier,
getPokeballTintColor,
} from "#app/data/pokeball";
import { PlayerGender } from "#enums/player-gender";
import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims";
import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect";
@ -13,11 +18,15 @@ import { Mode } from "#app/ui/ui";
import type { PartyOption } from "#app/ui/party-ui-handler";
import { PartyUiMode } from "#app/ui/party-ui-handler";
import { Species } from "#enums/species";
import type { Type } from "#enums/type";
import type { PokemonType } from "#enums/pokemon-type";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import {
getEncounterText,
queueEncounterMessage,
showEncounterText,
} from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
@ -41,18 +50,41 @@ export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1;
* @param shiny
* @param variant
*/
export function getSpriteKeysFromSpecies(species: Species, female?: boolean, formIndex?: number, shiny?: boolean, variant?: number): { spriteKey: string, fileRoot: string } {
const spriteKey = getPokemonSpecies(species).getSpriteKey(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
const fileRoot = getPokemonSpecies(species).getSpriteAtlasPath(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
export function getSpriteKeysFromSpecies(
species: Species,
female?: boolean,
formIndex?: number,
shiny?: boolean,
variant?: number,
): { spriteKey: string; fileRoot: string } {
const spriteKey = getPokemonSpecies(species).getSpriteKey(
female ?? false,
formIndex ?? 0,
shiny ?? false,
variant ?? 0,
);
const fileRoot = getPokemonSpecies(species).getSpriteAtlasPath(
female ?? false,
formIndex ?? 0,
shiny ?? false,
variant ?? 0,
);
return { spriteKey, fileRoot };
}
/**
* Gets the sprite key and file root for a given Pokemon (accounts for gender, shiny, variants, forms, and experimental)
*/
export function getSpriteKeysFromPokemon(pokemon: Pokemon): { spriteKey: string, fileRoot: string } {
const spriteKey = pokemon.getSpeciesForm().getSpriteKey(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant);
const fileRoot = pokemon.getSpeciesForm().getSpriteAtlasPath(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant);
export function getSpriteKeysFromPokemon(pokemon: Pokemon): {
spriteKey: string;
fileRoot: string;
} {
const spriteKey = pokemon
.getSpeciesForm()
.getSpriteKey(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant);
const fileRoot = pokemon
.getSpeciesForm()
.getSpriteAtlasPath(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant);
return { spriteKey, fileRoot };
}
@ -65,7 +97,11 @@ export function getSpriteKeysFromPokemon(pokemon: Pokemon): { spriteKey: string,
* @param doNotReturnLastAllowedMon Default `false`. If `true`, will never return the last unfainted pokemon in the party. Useful when this function is being used to determine what Pokemon to remove from the party (Don't want to remove last unfainted)
* @returns
*/
export function getRandomPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false, doNotReturnLastAllowedMon: boolean = false): PlayerPokemon {
export function getRandomPlayerPokemon(
isAllowed = false,
isFainted = false,
doNotReturnLastAllowedMon = false,
): PlayerPokemon {
const party = globalScene.getPlayerParty();
let chosenIndex: number;
let chosenPokemon: PlayerPokemon | null = null;
@ -104,7 +140,7 @@ export function getRandomPlayerPokemon(isAllowed: boolean = false, isFainted: bo
* @param isFainted Default false. If true, includes fainted mons.
* @returns
*/
export function getHighestLevelPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
export function getHighestLevelPlayerPokemon(isAllowed = false, isFainted = false): PlayerPokemon {
const party = globalScene.getPlayerParty();
let pokemon: PlayerPokemon | null = null;
@ -116,7 +152,7 @@ export function getHighestLevelPlayerPokemon(isAllowed: boolean = false, isFaint
continue;
}
pokemon = pokemon ? pokemon?.level < p?.level ? p : pokemon : p;
pokemon = pokemon ? (pokemon?.level < p?.level ? p : pokemon) : p;
}
return pokemon!;
@ -130,7 +166,7 @@ export function getHighestLevelPlayerPokemon(isAllowed: boolean = false, isFaint
* @param isFainted Default false. If true, includes fainted mons.
* @returns
*/
export function getHighestStatPlayerPokemon(stat: PermanentStat, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
export function getHighestStatPlayerPokemon(stat: PermanentStat, isAllowed = false, isFainted = false): PlayerPokemon {
const party = globalScene.getPlayerParty();
let pokemon: PlayerPokemon | null = null;
@ -142,7 +178,7 @@ export function getHighestStatPlayerPokemon(stat: PermanentStat, isAllowed: bool
continue;
}
pokemon = pokemon ? pokemon.getStat(stat) < p?.getStat(stat) ? p : pokemon : p;
pokemon = pokemon ? (pokemon.getStat(stat) < p?.getStat(stat) ? p : pokemon) : p;
}
return pokemon!;
@ -155,7 +191,7 @@ export function getHighestStatPlayerPokemon(stat: PermanentStat, isAllowed: bool
* @param isFainted Default false. If true, includes fainted mons.
* @returns
*/
export function getLowestLevelPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
export function getLowestLevelPlayerPokemon(isAllowed = false, isFainted = false): PlayerPokemon {
const party = globalScene.getPlayerParty();
let pokemon: PlayerPokemon | null = null;
@ -167,7 +203,7 @@ export function getLowestLevelPlayerPokemon(isAllowed: boolean = false, isFainte
continue;
}
pokemon = pokemon ? pokemon?.level > p?.level ? p : pokemon : p;
pokemon = pokemon ? (pokemon?.level > p?.level ? p : pokemon) : p;
}
return pokemon!;
@ -180,7 +216,7 @@ export function getLowestLevelPlayerPokemon(isAllowed: boolean = false, isFainte
* @param isFainted Default false. If true, includes fainted mons.
* @returns
*/
export function getHighestStatTotalPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
export function getHighestStatTotalPlayerPokemon(isAllowed = false, isFainted = false): PlayerPokemon {
const party = globalScene.getPlayerParty();
let pokemon: PlayerPokemon | null = null;
@ -192,7 +228,7 @@ export function getHighestStatTotalPlayerPokemon(isAllowed: boolean = false, isF
continue;
}
pokemon = pokemon ? pokemon?.stats.reduce((a, b) => a + b) < p?.stats.reduce((a, b) => a + b) ? p : pokemon : p;
pokemon = pokemon ? (pokemon?.stats.reduce((a, b) => a + b) < p?.stats.reduce((a, b) => a + b) ? p : pokemon) : p;
}
return pokemon!;
@ -209,28 +245,40 @@ export function getHighestStatTotalPlayerPokemon(isAllowed: boolean = false, isF
* @param allowMythical
* @returns
*/
export function getRandomSpeciesByStarterCost(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[], allowSubLegendary: boolean = true, allowLegendary: boolean = true, allowMythical: boolean = true): Species {
export function getRandomSpeciesByStarterCost(
starterTiers: number | [number, number],
excludedSpecies?: Species[],
types?: PokemonType[],
allowSubLegendary = true,
allowLegendary = true,
allowMythical = true,
): Species {
let min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers;
let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers;
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarterCosts)
.map(s => [ parseInt(s) as Species, speciesStarterCosts[s] as number ])
.map(s => [Number.parseInt(s) as Species, speciesStarterCosts[s] as number])
.filter(s => {
const pokemonSpecies = getPokemonSpecies(s[0]);
return pokemonSpecies && (!excludedSpecies || !excludedSpecies.includes(s[0]))
&& (allowSubLegendary || !pokemonSpecies.subLegendary)
&& (allowLegendary || !pokemonSpecies.legendary)
&& (allowMythical || !pokemonSpecies.mythical);
return (
pokemonSpecies &&
(!excludedSpecies || !excludedSpecies.includes(s[0])) &&
(allowSubLegendary || !pokemonSpecies.subLegendary) &&
(allowLegendary || !pokemonSpecies.legendary) &&
(allowMythical || !pokemonSpecies.mythical)
);
})
.map(s => [getPokemonSpecies(s[0]), s[1]]);
if (types && types.length > 0) {
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2)));
filteredSpecies = filteredSpecies.filter(
s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2)),
);
}
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are
// Starts by decrementing starter tier min until it is 0, then increments tier max up to 10
let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => (s[1] >= min && s[1] <= max));
let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => s[1] >= min && s[1] <= max);
while (tryFilterStarterTiers.length === 0 && !(min === 0 && max === 10)) {
if (min > 0) {
min--;
@ -259,7 +307,11 @@ export function koPlayerPokemon(pokemon: PlayerPokemon) {
pokemon.hp = 0;
pokemon.trySetStatus(StatusEffect.FAINT);
pokemon.updateInfo();
queueEncounterMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
queueEncounterMessage(
i18next.t("battle:fainted", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}),
);
}
/**
@ -290,7 +342,9 @@ function applyHpChangeToPokemon(pokemon: PlayerPokemon, value: number) {
*/
export function applyDamageToPokemon(pokemon: PlayerPokemon, damage: number) {
if (damage <= 0) {
console.warn("Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead.");
console.warn(
"Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead.",
);
}
// If a Pokemon would faint from the damage applied, its HP is instead set to 1.
if (pokemon.isAllowedInBattle() && pokemon.hp - damage <= 0) {
@ -308,7 +362,9 @@ export function applyDamageToPokemon(pokemon: PlayerPokemon, damage: number) {
*/
export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) {
if (heal <= 0) {
console.warn("Damaging pokemon with `applyHealToPokemon` is not recommended! Please use `applyDamageToPokemon` instead.");
console.warn(
"Damaging pokemon with `applyHealToPokemon` is not recommended! Please use `applyDamageToPokemon` instead.",
);
}
applyHpChangeToPokemon(pokemon, heal);
@ -321,7 +377,8 @@ export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) {
* @param value
*/
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE()
const modType = modifierTypes
.MYSTERY_ENCOUNTER_SHUCKLE_JUICE()
.generateType(globalScene.getPlayerParty(), [value])
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE);
const modifier = modType?.newModifier(pokemon);
@ -339,15 +396,20 @@ export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: numb
* @param modType
* @param fallbackModifierType
*/
export async function applyModifierTypeToPlayerPokemon(pokemon: PlayerPokemon, modType: PokemonHeldItemModifierType, fallbackModifierType?: PokemonHeldItemModifierType) {
export async function applyModifierTypeToPlayerPokemon(
pokemon: PlayerPokemon,
modType: PokemonHeldItemModifierType,
fallbackModifierType?: PokemonHeldItemModifierType,
) {
// Check if the Pokemon has max stacks of that item already
const modifier = modType.newModifier(pokemon);
const existing = globalScene.findModifier(m => (
const existing = globalScene.findModifier(
m =>
m instanceof PokemonHeldItemModifier &&
m.type.id === modType.id &&
m.pokemonId === pokemon.id &&
m.matchType(modifier)
)) as PokemonHeldItemModifier;
m.matchType(modifier),
) as PokemonHeldItemModifier;
// At max stacks
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
@ -373,7 +435,11 @@ export async function applyModifierTypeToPlayerPokemon(pokemon: PlayerPokemon, m
* @param pokeballType
* @param ballTwitchRate - can pass custom ball catch rates (for special events, like safari)
*/
export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: PokeballType, ballTwitchRate?: number): Promise<boolean> {
export function trainerThrowPokeball(
pokemon: EnemyPokemon,
pokeballType: PokeballType,
ballTwitchRate?: number,
): Promise<boolean> {
const originalY: number = pokemon.y;
if (!ballTwitchRate) {
@ -397,7 +463,9 @@ export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: Pokeba
});
return new Promise(resolve => {
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
globalScene.trainer.setTexture(
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`,
);
globalScene.time.delayedCall(512, () => {
globalScene.playSound("se/pb_throw");
@ -406,7 +474,9 @@ export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: Pokeba
globalScene.time.delayedCall(256, () => {
globalScene.trainer.setFrame("3");
globalScene.time.delayedCall(768, () => {
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
globalScene.trainer.setTexture(
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`,
);
});
});
@ -486,22 +556,22 @@ export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: Pokeba
alpha: 0,
duration: 200,
easing: "Sine.easeIn",
onComplete: () => pbTint.destroy()
onComplete: () => pbTint.destroy(),
});
}
},
});
}
},
onComplete: () => {
catchPokemon(pokemon, pokeball, pokeballType).then(() => resolve(true));
}
},
});
};
globalScene.time.delayedCall(250, () => doPokeballBounceAnim(pokeball, 16, 72, 350, doShake));
}
},
});
}
},
});
});
});
@ -515,7 +585,12 @@ export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: Pokeba
* @param pokeball
* @param pokeballType
*/
function failCatch(pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType) {
function failCatch(
pokemon: EnemyPokemon,
originalY: number,
pokeball: Phaser.GameObjects.Sprite,
pokeballType: PokeballType,
) {
return new Promise<void>(resolve => {
globalScene.playSound("se/pb_rel");
pokemon.setY(originalY);
@ -534,13 +609,21 @@ function failCatch(pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.Ga
targets: pokemon,
duration: 250,
ease: "Sine.easeOut",
scale: 1
scale: 1,
});
globalScene.currentBattle.lastUsedPokeball = pokeballType;
removePb(pokeball);
globalScene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.getNameToRender() }), null, () => resolve(), null, true);
globalScene.ui.showText(
i18next.t("battle:pokemonBrokeFree", {
pokemonName: pokemon.getNameToRender(),
}),
null,
() => resolve(),
null,
true,
);
});
}
@ -553,10 +636,19 @@ function failCatch(pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.Ga
* @param showCatchObtainMessage
* @param isObtain
*/
export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite | null, pokeballType: PokeballType, showCatchObtainMessage: boolean = true, isObtain: boolean = false): Promise<void> {
export async function catchPokemon(
pokemon: EnemyPokemon,
pokeball: Phaser.GameObjects.Sprite | null,
pokeballType: PokeballType,
showCatchObtainMessage = true,
isObtain = false,
): Promise<void> {
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) {
if (
speciesForm.abilityHidden &&
(pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1
) {
globalScene.validateAchv(achvs.HIDDEN_ABILITY);
}
@ -614,17 +706,67 @@ export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameO
Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => {
if (globalScene.getPlayerParty().length === 6) {
const promptRelease = () => {
globalScene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => {
globalScene.ui.showText(
i18next.t("battle:partyFull", {
pokemonName: pokemon.getNameToRender(),
}),
null,
() => {
globalScene.pokemonInfoContainer.makeRoomForConfirmUi(1, true);
globalScene.ui.setMode(Mode.CONFIRM, () => {
const newPokemon = globalScene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon);
globalScene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => {
globalScene.ui.setMode(
Mode.CONFIRM,
() => {
const newPokemon = globalScene.addPlayerPokemon(
pokemon.species,
pokemon.level,
pokemon.abilityIndex,
pokemon.formIndex,
pokemon.gender,
pokemon.shiny,
pokemon.variant,
pokemon.ivs,
pokemon.nature,
pokemon,
);
globalScene.ui.setMode(
Mode.SUMMARY,
newPokemon,
0,
SummaryUiMode.DEFAULT,
() => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
promptRelease();
});
}, false);
}, () => {
globalScene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: number, _option: PartyOption) => {
},
false,
);
},
() => {
const attributes = {
shiny: pokemon.shiny,
variant: pokemon.variant,
form: pokemon.formIndex,
female: pokemon.gender === Gender.FEMALE,
};
globalScene.ui.setOverlayMode(
Mode.POKEDEX_PAGE,
pokemon.species,
pokemon.formIndex,
attributes,
null,
() => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
promptRelease();
});
},
);
},
() => {
globalScene.ui.setMode(
Mode.PARTY,
PartyUiMode.RELEASE,
0,
(slotIndex: number, _option: PartyOption) => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
if (slotIndex < 6) {
addToParty(slotIndex);
@ -632,14 +774,19 @@ export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameO
promptRelease();
}
});
});
}, () => {
},
);
},
() => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
removePokemon();
end();
});
}, "fullParty");
});
},
"fullParty",
);
},
);
};
promptRelease();
} else {
@ -649,7 +796,15 @@ export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameO
};
if (showCatchObtainMessage) {
globalScene.ui.showText(i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", { pokemonName: pokemon.getNameToRender() }), null, doPokemonCatchMenu, 0, true);
globalScene.ui.showText(
i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", {
pokemonName: pokemon.getNameToRender(),
}),
null,
doPokemonCatchMenu,
0,
true,
);
} else {
doPokemonCatchMenu();
}
@ -671,7 +826,7 @@ function removePb(pokeball: Phaser.GameObjects.Sprite) {
alpha: 0,
onComplete: () => {
pokeball.destroy();
}
},
});
}
}
@ -696,11 +851,17 @@ export async function doPokemonFlee(pokemon: EnemyPokemon): Promise<void> {
onComplete: () => {
pokemon.setVisible(false);
pokemon.leaveField(true, true, true);
showEncounterText(i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false)
.then(() => {
showEncounterText(
i18next.t("battle:pokemonFled", {
pokemonName: pokemon.getNameToRender(),
}),
null,
600,
false,
).then(() => {
resolve();
});
}
},
});
});
}
@ -724,11 +885,17 @@ export function doPlayerFlee(pokemon: EnemyPokemon): Promise<void> {
onComplete: () => {
pokemon.setVisible(false);
pokemon.leaveField(true, true, true);
showEncounterText(i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false)
.then(() => {
showEncounterText(
i18next.t("battle:playerFled", {
pokemonName: pokemon.getNameToRender(),
}),
null,
600,
false,
).then(() => {
resolve();
});
}
},
});
});
}
@ -792,7 +959,7 @@ export function getGoldenBugNetSpecies(level: number): PokemonSpecies {
* @param scene
* @param levelAdditiveModifier Default 0. will add +(1 level / 10 waves * levelAdditiveModifier) to the level calculation
*/
export function getEncounterPokemonLevelForWave(levelAdditiveModifier: number = 0) {
export function getEncounterPokemonLevelForWave(levelAdditiveModifier = 0) {
const currentBattle = globalScene.currentBattle;
const baseLevel = currentBattle.getLevelForWave();
@ -803,7 +970,10 @@ export function getEncounterPokemonLevelForWave(levelAdditiveModifier: number =
export async function addPokemonDataToDexAndValidateAchievements(pokemon: PlayerPokemon) {
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) {
if (
speciesForm.abilityHidden &&
(pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1
) {
globalScene.validateAchv(achvs.HIDDEN_ABILITY);
}
@ -832,9 +1002,16 @@ export async function addPokemonDataToDexAndValidateAchievements(pokemon: Player
* @param scene
* @param invalidSelectionKey
*/
export function isPokemonValidForEncounterOptionSelection(pokemon: Pokemon, invalidSelectionKey: string): string | null {
export function isPokemonValidForEncounterOptionSelection(
pokemon: Pokemon,
invalidSelectionKey: string,
): string | null {
if (!pokemon.isAllowedInChallenge()) {
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
return (
i18next.t("partyUiHandler:cantBeUsed", {
pokemonName: pokemon.getNameToRender(),
}) ?? null
);
}
if (!pokemon.isAllowedInBattle()) {
return getEncounterText(invalidSelectionKey) ?? null;

View File

@ -7,7 +7,7 @@ import { globalScene } from "#app/global-scene";
export enum TransformationScreenPosition {
CENTER,
LEFT,
RIGHT
RIGHT,
}
/**
@ -17,7 +17,11 @@ export enum TransformationScreenPosition {
* @param transformPokemon
* @param screenPosition
*/
export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon, transformPokemon: PlayerPokemon, screenPosition: TransformationScreenPosition) {
export function doPokemonTransformationSequence(
previousPokemon: PlayerPokemon,
transformPokemon: PlayerPokemon,
screenPosition: TransformationScreenPosition,
) {
return new Promise<void>(resolve => {
const transformationContainer = globalScene.fieldUI.getByName("Dream Background") as Phaser.GameObjects.Container;
const transformationBaseBg = globalScene.add.image(0, 0, "default_bg");
@ -30,14 +34,26 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
let pokemonEvoSprite: Phaser.GameObjects.Sprite;
let pokemonEvoTintSprite: Phaser.GameObjects.Sprite;
const xOffset = screenPosition === TransformationScreenPosition.CENTER ? 0 :
screenPosition === TransformationScreenPosition.RIGHT ? 100 : -100;
const xOffset =
screenPosition === TransformationScreenPosition.CENTER
? 0
: screenPosition === TransformationScreenPosition.RIGHT
? 100
: -100;
// Centered transformations occur at a lower y Position
const yOffset = screenPosition !== TransformationScreenPosition.CENTER ? -15 : 0;
const getPokemonSprite = () => {
const ret = globalScene.addPokemonSprite(previousPokemon, transformationBaseBg.displayWidth / 2 + xOffset, transformationBaseBg.displayHeight / 2 + yOffset, "pkmn__sub");
ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
const ret = globalScene.addPokemonSprite(
previousPokemon,
transformationBaseBg.displayWidth / 2 + xOffset,
transformationBaseBg.displayHeight / 2 + yOffset,
"pkmn__sub",
);
ret.setPipeline(globalScene.spritePipeline, {
tone: [0.0, 0.0, 0.0, 0.0],
ignoreTimeTint: true,
});
return ret;
};
@ -48,10 +64,10 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
pokemonSprite.setAlpha(0);
pokemonTintSprite.setAlpha(0);
pokemonTintSprite.setTintFill(0xFFFFFF);
pokemonTintSprite.setTintFill(0xffffff);
pokemonEvoSprite.setVisible(false);
pokemonEvoTintSprite.setVisible(false);
pokemonEvoTintSprite.setTintFill(0xFFFFFF);
pokemonEvoTintSprite.setTintFill(0xffffff);
[pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite].map(sprite => {
const spriteKey = previousPokemon.getSpriteKey(true);
@ -61,7 +77,12 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
console.error(`Failed to play animation for ${spriteKey}`, err);
}
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()), isTerastallized: previousPokemon.isTerastallized });
sprite.setPipeline(globalScene.spritePipeline, {
tone: [0.0, 0.0, 0.0, 0.0],
hasShadow: false,
teraColor: getTypeRgb(previousPokemon.getTeraType()),
isTerastallized: previousPokemon.isTerastallized,
});
sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey());
sprite.setPipelineData("shiny", previousPokemon.shiny);
@ -139,18 +160,18 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
previousPokemon.destroy();
transformPokemon.setVisible(false);
transformPokemon.setAlpha(1);
}
},
});
});
}
},
});
});
});
});
});
}
},
});
}
},
});
});
}
@ -163,7 +184,12 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
* @param xOffset
* @param yOffset
*/
function doSpiralUpward(transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
function doSpiralUpward(
transformationBaseBg: Phaser.GameObjects.Image,
transformationContainer: Phaser.GameObjects.Container,
xOffset: number,
yOffset: number,
) {
let f = 0;
globalScene.tweens.addCounter({
@ -173,12 +199,18 @@ function doSpiralUpward(transformationBaseBg: Phaser.GameObjects.Image, transfor
if (f < 64) {
if (!(f & 7)) {
for (let i = 0; i < 4; i++) {
doSpiralUpwardParticle((f & 120) * 2 + i * 64, transformationBaseBg, transformationContainer, xOffset, yOffset);
doSpiralUpwardParticle(
(f & 120) * 2 + i * 64,
transformationBaseBg,
transformationContainer,
xOffset,
yOffset,
);
}
}
f++;
}
}
},
});
}
@ -190,7 +222,12 @@ function doSpiralUpward(transformationBaseBg: Phaser.GameObjects.Image, transfor
* @param xOffset
* @param yOffset
*/
function doArcDownward(transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
function doArcDownward(
transformationBaseBg: Phaser.GameObjects.Image,
transformationContainer: Phaser.GameObjects.Container,
xOffset: number,
yOffset: number,
) {
let f = 0;
globalScene.tweens.addCounter({
@ -205,7 +242,7 @@ function doArcDownward(transformationBaseBg: Phaser.GameObjects.Image, transform
}
f++;
}
}
},
});
}
@ -217,7 +254,12 @@ function doArcDownward(transformationBaseBg: Phaser.GameObjects.Image, transform
* @param pokemonTintSprite
* @param pokemonEvoTintSprite
*/
function doCycle(l: number, lastCycle: number, pokemonTintSprite: Phaser.GameObjects.Sprite, pokemonEvoTintSprite: Phaser.GameObjects.Sprite): Promise<boolean> {
function doCycle(
l: number,
lastCycle: number,
pokemonTintSprite: Phaser.GameObjects.Sprite,
pokemonEvoTintSprite: Phaser.GameObjects.Sprite,
): Promise<boolean> {
return new Promise(resolve => {
const isLastCycle = l === lastCycle;
globalScene.tweens.add({
@ -225,7 +267,7 @@ function doCycle(l: number, lastCycle: number, pokemonTintSprite: Phaser.GameObj
scale: 0.25,
ease: "Cubic.easeInOut",
duration: 500 / l,
yoyo: !isLastCycle
yoyo: !isLastCycle,
});
globalScene.tweens.add({
targets: pokemonEvoTintSprite,
@ -240,7 +282,7 @@ function doCycle(l: number, lastCycle: number, pokemonTintSprite: Phaser.GameObj
pokemonTintSprite.setVisible(false);
resolve(true);
}
}
},
});
});
}
@ -253,7 +295,12 @@ function doCycle(l: number, lastCycle: number, pokemonTintSprite: Phaser.GameObj
* @param xOffset
* @param yOffset
*/
function doCircleInward(transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
function doCircleInward(
transformationBaseBg: Phaser.GameObjects.Image,
transformationContainer: Phaser.GameObjects.Container,
xOffset: number,
yOffset: number,
) {
let f = 0;
globalScene.tweens.addCounter({
@ -270,7 +317,7 @@ function doCircleInward(transformationBaseBg: Phaser.GameObjects.Image, transfor
}
}
f++;
}
},
});
}
@ -283,7 +330,13 @@ function doCircleInward(transformationBaseBg: Phaser.GameObjects.Image, transfor
* @param xOffset
* @param yOffset
*/
function doSpiralUpwardParticle(trigIndex: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
function doSpiralUpwardParticle(
trigIndex: number,
transformationBaseBg: Phaser.GameObjects.Image,
transformationContainer: Phaser.GameObjects.Container,
xOffset: number,
yOffset: number,
) {
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
const particle = globalScene.add.image(initialX, 0, "evo_sparkle");
transformationContainer.add(particle);
@ -296,7 +349,7 @@ function doSpiralUpwardParticle(trigIndex: number, transformationBaseBg: Phaser.
duration: getFrameMs(1),
onRepeat: () => {
updateParticle();
}
},
});
const updateParticle = () => {
@ -304,7 +357,7 @@ function doSpiralUpwardParticle(trigIndex: number, transformationBaseBg: Phaser.
particle.setPosition(initialX, 88 - (f * f) / 80 + yOffset);
particle.y += sin(trigIndex, amp) / 4;
particle.x += cos(trigIndex, amp);
particle.setScale(1 - (f / 80));
particle.setScale(1 - f / 80);
trigIndex += 4;
if (f & 1) {
amp--;
@ -328,7 +381,13 @@ function doSpiralUpwardParticle(trigIndex: number, transformationBaseBg: Phaser.
* @param xOffset
* @param yOffset
*/
function doArcDownParticle(trigIndex: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
function doArcDownParticle(
trigIndex: number,
transformationBaseBg: Phaser.GameObjects.Image,
transformationContainer: Phaser.GameObjects.Container,
xOffset: number,
yOffset: number,
) {
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
const particle = globalScene.add.image(initialX, 0, "evo_sparkle");
particle.setScale(0.5);
@ -342,7 +401,7 @@ function doArcDownParticle(trigIndex: number, transformationBaseBg: Phaser.GameO
duration: getFrameMs(1),
onRepeat: () => {
updateParticle();
}
},
});
const updateParticle = () => {
@ -371,7 +430,14 @@ function doArcDownParticle(trigIndex: number, transformationBaseBg: Phaser.GameO
* @param xOffset
* @param yOffset
*/
function doCircleInwardParticle(trigIndex: number, speed: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
function doCircleInwardParticle(
trigIndex: number,
speed: number,
transformationBaseBg: Phaser.GameObjects.Image,
transformationContainer: Phaser.GameObjects.Container,
xOffset: number,
yOffset: number,
) {
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
const initialY = transformationBaseBg.displayHeight / 2 + yOffset;
const particle = globalScene.add.image(initialX, initialY, "evo_sparkle");
@ -384,7 +450,7 @@ function doCircleInwardParticle(trigIndex: number, speed: number, transformation
duration: getFrameMs(1),
onRepeat: () => {
updateParticle();
}
},
});
const updateParticle = () => {

View File

@ -5,11 +5,17 @@ import { UiTheme } from "#enums/ui-theme";
import i18next from "i18next";
import { Stat, EFFECTIVE_STATS, getShortenedStatKey } from "#enums/stat";
export function getNatureName(nature: Nature, includeStatEffects: boolean = false, forStarterSelect: boolean = false, ignoreBBCode: boolean = false, uiTheme: UiTheme = UiTheme.DEFAULT): string {
export function getNatureName(
nature: Nature,
includeStatEffects = false,
forStarterSelect = false,
ignoreBBCode = false,
uiTheme: UiTheme = UiTheme.DEFAULT,
): string {
let ret = Utils.toReadableString(Nature[nature]);
//Translating nature
if (i18next.exists("nature:" + ret)) {
ret = i18next.t("nature:" + ret as any);
if (i18next.exists(`nature:${ret}`)) {
ret = i18next.t(`nature:${ret}` as any);
}
if (includeStatEffects) {
let increasedStat: Stat | null = null;
@ -23,7 +29,9 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
}
}
const textStyle = forStarterSelect ? TextStyle.SUMMARY_ALT : TextStyle.WINDOW;
const getTextFrag = !ignoreBBCode ? (text: string, style: TextStyle) => getBBCodeFrag(text, style, uiTheme) : (text: string, style: TextStyle) => text;
const getTextFrag = !ignoreBBCode
? (text: string, style: TextStyle) => getBBCodeFrag(text, style, uiTheme)
: (text: string, _style: TextStyle) => text;
if (increasedStat && decreasedStat) {
ret = `${getTextFrag(`${ret}${!forStarterSelect ? "\n" : " "}(`, textStyle)}${getTextFrag(`+${i18next.t(getShortenedStatKey(increasedStat))}`, TextStyle.SUMMARY_PINK)}${getTextFrag("/", textStyle)}${getTextFrag(`-${i18next.t(getShortenedStatKey(decreasedStat))}`, TextStyle.SUMMARY_BLUE)}${getTextFrag(")", textStyle)}`;
} else {

View File

@ -95,16 +95,31 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number {
const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr);
const catchingCharmMultiplier = new NumberHolder(1);
globalScene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier);
const dexMultiplier = globalScene.gameMode.isDaily || dexCount > 800 ? 2.5
: dexCount > 600 ? 2
: dexCount > 400 ? 1.5
: dexCount > 200 ? 1
: dexCount > 100 ? 0.5
const dexMultiplier =
globalScene.gameMode.isDaily || dexCount > 800
? 2.5
: dexCount > 600
? 2
: dexCount > 400
? 1.5
: dexCount > 200
? 1
: dexCount > 100
? 0.5
: 0;
return Math.floor(catchingCharmMultiplier.value * dexMultiplier * Math.min(255, modifiedCatchRate) / 6);
return Math.floor((catchingCharmMultiplier.value * dexMultiplier * Math.min(255, modifiedCatchRate)) / 6);
}
export function doPokeballBounceAnim(pokeball: Phaser.GameObjects.Sprite, y1: number, y2: number, baseBounceDuration: number, callback: Function, isCritical: boolean = false) {
// TODO: fix Function annotations
export function doPokeballBounceAnim(
pokeball: Phaser.GameObjects.Sprite,
y1: number,
y2: number,
baseBounceDuration: number,
// biome-ignore lint/complexity/noBannedTypes: TODO
callback: Function,
isCritical = false,
) {
let bouncePower = 1;
let bounceYOffset = y1;
let bounceY = y2;
@ -134,12 +149,12 @@ export function doPokeballBounceAnim(pokeball: Phaser.GameObjects.Sprite, y1: nu
y: bounceY,
duration: bouncePower * baseBounceDuration,
ease: "Cubic.easeOut",
onComplete: () => doBounce()
onComplete: () => doBounce(),
});
} else if (callback) {
callback();
}
}
},
});
};
@ -165,12 +180,12 @@ export function doPokeballBounceAnim(pokeball: Phaser.GameObjects.Sprite, y1: nu
x: x0,
duration: 60,
ease: "Linear",
onComplete: () => globalScene.time.delayedCall(500, doBounce)
onComplete: () => globalScene.time.delayedCall(500, doBounce),
});
}
}
},
});
}
},
});
};

View File

@ -1,7 +1,8 @@
import { PokemonFormChangeItemModifier } from "../modifier/modifier";
import type Pokemon from "../field/pokemon";
import { StatusEffect } from "#enums/status-effect";
import { MoveCategory, allMoves } from "./move";
import { allMoves } from "./moves/move";
import { MoveCategory } from "#enums/MoveCategory";
import type { Constructor, nil } from "#app/utils";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
@ -131,7 +132,7 @@ export enum FormChangeItem {
DRAGON_MEMORY,
DARK_MEMORY,
FAIRY_MEMORY,
NORMAL_MEMORY // TODO: Find a potential use for this
NORMAL_MEMORY, // TODO: Find a potential use for this
}
export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
@ -145,7 +146,14 @@ export class SpeciesFormChange {
public quiet: boolean;
public readonly conditions: SpeciesFormChangeCondition[];
constructor(speciesId: Species, preFormKey: string, evoFormKey: string, trigger: SpeciesFormChangeTrigger, quiet: boolean = false, ...conditions: SpeciesFormChangeCondition[]) {
constructor(
speciesId: Species,
preFormKey: string,
evoFormKey: string,
trigger: SpeciesFormChangeTrigger,
quiet = false,
...conditions: SpeciesFormChangeCondition[]
) {
this.speciesId = speciesId;
this.preFormKey = preFormKey;
this.formKey = evoFormKey;
@ -211,9 +219,9 @@ export class SpeciesFormChangeCondition {
}
export abstract class SpeciesFormChangeTrigger {
public description: string = "";
public description = "";
canChange(pokemon: Pokemon): boolean {
canChange(_pokemon: Pokemon): boolean {
return true;
}
@ -222,20 +230,22 @@ export abstract class SpeciesFormChangeTrigger {
}
}
export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {
}
export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {}
export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger {
public description: string = i18next.t("pokemonEvolutions:Forms.ability");
}
export class SpeciesFormChangeCompoundTrigger {
public description: string = "";
public description = "";
public triggers: SpeciesFormChangeTrigger[];
constructor(...triggers: SpeciesFormChangeTrigger[]) {
this.triggers = triggers;
this.description = this.triggers.filter(trigger => trigger?.description?.length > 0).map(trigger => trigger.description).join(", ");
this.description = this.triggers
.filter(trigger => trigger?.description?.length > 0)
.map(trigger => trigger.description)
.join(", ");
}
canChange(pokemon: Pokemon): boolean {
@ -257,17 +267,27 @@ export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger {
public item: FormChangeItem;
public active: boolean;
constructor(item: FormChangeItem, active: boolean = true) {
constructor(item: FormChangeItem, active = true) {
super();
this.item = item;
this.active = active;
this.description = this.active ?
i18next.t("pokemonEvolutions:Forms.item", { item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`) }) :
i18next.t("pokemonEvolutions:Forms.deactivateItem", { item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`) });
this.description = this.active
? i18next.t("pokemonEvolutions:Forms.item", {
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
})
: i18next.t("pokemonEvolutions:Forms.deactivateItem", {
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
});
}
canChange(pokemon: Pokemon): boolean {
return !!globalScene.findModifier(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id && m.formChangeItem === this.item && m.active === this.active);
return !!globalScene.findModifier(
m =>
m instanceof PokemonFormChangeItemModifier &&
m.pokemonId === pokemon.id &&
m.formChangeItem === this.item &&
m.active === this.active,
);
}
}
@ -280,7 +300,7 @@ export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger
this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay");
}
canChange(pokemon: Pokemon): boolean {
canChange(_pokemon: Pokemon): boolean {
return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1;
}
}
@ -288,10 +308,12 @@ export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger
export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger {
public active: boolean;
constructor(active: boolean = false) {
constructor(active = false) {
super();
this.active = active;
this.description = this.active ? i18next.t("pokemonEvolutions:Forms.enter") : i18next.t("pokemonEvolutions:Forms.leave");
this.description = this.active
? i18next.t("pokemonEvolutions:Forms.enter")
: i18next.t("pokemonEvolutions:Forms.leave");
}
canChange(pokemon: Pokemon): boolean {
@ -303,7 +325,7 @@ export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigg
public statusEffects: StatusEffect[];
public invert: boolean;
constructor(statusEffects: StatusEffect | StatusEffect[], invert: boolean = false) {
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
super();
if (!Array.isArray(statusEffects)) {
statusEffects = [statusEffects];
@ -314,7 +336,7 @@ export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigg
}
canChange(pokemon: Pokemon): boolean {
return (this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1) !== this.invert;
return this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1 !== this.invert;
}
}
@ -322,17 +344,26 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge
public move: Moves;
public known: boolean;
constructor(move: Moves, known: boolean = true) {
constructor(move: Moves, known = true) {
super();
this.move = move;
this.known = known;
const moveKey = Moves[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("") as unknown as string;
this.description = known ? i18next.t("pokemonEvolutions:Forms.moveLearned", { move: i18next.t(`move:${moveKey}.name`) }) :
i18next.t("pokemonEvolutions:Forms.moveForgotten", { move: i18next.t(`move:${moveKey}.name`) });
const moveKey = Moves[this.move]
.split("_")
.filter(f => f)
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("") as unknown as string;
this.description = known
? i18next.t("pokemonEvolutions:Forms.moveLearned", {
move: i18next.t(`move:${moveKey}.name`),
})
: i18next.t("pokemonEvolutions:Forms.moveForgotten", {
move: i18next.t(`move:${moveKey}.name`),
});
}
canChange(pokemon: Pokemon): boolean {
return (!!pokemon.moveset.filter(m => m?.moveId === this.move).length) === this.known;
return !!pokemon.moveset.filter(m => m?.moveId === this.move).length === this.known;
}
}
@ -340,7 +371,7 @@ export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrig
public movePredicate: (m: Moves) => boolean;
public used: boolean;
constructor(move: Moves | ((m: Moves) => boolean), used: boolean = true) {
constructor(move: Moves | ((m: Moves) => boolean), used = true) {
super();
this.movePredicate = typeof move === "function" ? move : (m: Moves) => m === move;
this.used = used;
@ -360,7 +391,9 @@ export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigg
description = i18next.t("pokemonEvolutions:Forms.postMove");
canChange(pokemon: Pokemon): boolean {
return pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used;
return (
pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used
);
}
}
@ -368,7 +401,7 @@ export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMove
override canChange(pokemon: Pokemon): boolean {
if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
return false;
} else {
}
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song
if (pokemon.hasAbility(Abilities.SHEER_FORCE)) {
return false;
@ -376,7 +409,6 @@ export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMove
return super.canChange(pokemon);
}
}
}
export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
private formKey: string;
@ -388,7 +420,11 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
}
canChange(pokemon: Pokemon): boolean {
return this.formKey === pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)].formKey;
return (
this.formKey ===
pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)]
.formKey
);
}
}
@ -439,7 +475,12 @@ export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
return !isAbilitySuppressed && !isWeatherSuppressed && (pokemon.hasAbility(this.ability) && this.weathers.includes(currentWeather));
return (
!isAbilitySuppressed &&
!isWeatherSuppressed &&
pokemon.hasAbility(this.ability) &&
this.weathers.includes(currentWeather)
);
}
}
@ -490,16 +531,27 @@ export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: Specie
const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1;
const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey;
if (isMega) {
return i18next.t("battlePokemonForm:megaChange", { preName, pokemonName: pokemon.name });
return i18next.t("battlePokemonForm:megaChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isGmax) {
return i18next.t("battlePokemonForm:gigantamaxChange", { preName, pokemonName: pokemon.name });
return i18next.t("battlePokemonForm:gigantamaxChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isEmax) {
return i18next.t("battlePokemonForm:eternamaxChange", { preName, pokemonName: pokemon.name });
return i18next.t("battlePokemonForm:eternamaxChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isRevert) {
return i18next.t("battlePokemonForm:revertChange", { pokemonName: getPokemonNameWithAffix(pokemon) });
return i18next.t("battlePokemonForm:revertChange", {
pokemonName: getPokemonNameWithAffix(pokemon),
});
}
if (pokemon.getAbility().id === Abilities.DISGUISE) {
return i18next.t("battlePokemonForm:disguiseChange");
@ -514,13 +566,14 @@ export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: Specie
* @returns A {@linkcode SpeciesFormChangeCondition} checking if that species is registered as caught
*/
function getSpeciesDependentFormChangeCondition(species: Species): SpeciesFormChangeCondition {
return new SpeciesFormChangeCondition(p => !!globalScene.gameData.dexData[species].caughtAttr);
return new SpeciesFormChangeCondition(_p => !!globalScene.gameData.dexData[species].caughtAttr);
}
interface PokemonFormChanges {
[key: string]: SpeciesFormChange[]
[key: string]: SpeciesFormChange[];
}
// biome-ignore format: manually formatted
export const pokemonFormChanges: PokemonFormChanges = {
[Species.VENUSAUR]: [
new SpeciesFormChange(Species.VENUSAUR, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.VENUSAURITE)),
@ -993,16 +1046,22 @@ export const pokemonFormChanges: PokemonFormChanges = {
export function initPokemonForms() {
const formChangeKeys = Object.keys(pokemonFormChanges);
formChangeKeys.forEach(pk => {
for (const pk of formChangeKeys) {
const formChanges = pokemonFormChanges[pk];
const newFormChanges: SpeciesFormChange[] = [];
for (const fc of formChanges) {
const itemTrigger = fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger;
if (itemTrigger && !formChanges.find(c => fc.formKey === c.preFormKey && fc.preFormKey === c.formKey)) {
newFormChanges.push(new SpeciesFormChange(fc.speciesId, fc.formKey, fc.preFormKey, new SpeciesFormChangeItemTrigger(itemTrigger.item, false)));
newFormChanges.push(
new SpeciesFormChange(
fc.speciesId,
fc.formKey,
fc.preFormKey,
new SpeciesFormChangeItemTrigger(itemTrigger.item, false),
),
);
}
}
formChanges.push(...newFormChanges);
});
}
}

File diff suppressed because it is too large Load Diff

View File

@ -185,13 +185,25 @@ const seasonalSplashMessages: Season[] = [
name: "Halloween",
start: "09-15",
end: "10-31",
messages: [ "halloween.pumpkabooAbout", "halloween.mayContainSpiders", "halloween.spookyScarySkeledirge", "halloween.gourgeistUsedTrickOrTreat", "halloween.letsSnuggleForever" ],
messages: [
"halloween.pumpkabooAbout",
"halloween.mayContainSpiders",
"halloween.spookyScarySkeledirge",
"halloween.gourgeistUsedTrickOrTreat",
"halloween.letsSnuggleForever",
],
},
{
name: "XMAS",
start: "12-01",
end: "12-26",
messages: [ "xmas.happyHolidays", "xmas.unaffilicatedWithDelibirdServices", "xmas.delibirdSeason", "xmas.diamondsFromTheSky", "xmas.holidayStylePikachuNotIncluded" ],
messages: [
"xmas.happyHolidays",
"xmas.unaffilicatedWithDelibirdServices",
"xmas.delibirdSeason",
"xmas.diamondsFromTheSky",
"xmas.holidayStylePikachuNotIncluded",
],
},
{
name: "New Year's",
@ -215,13 +227,13 @@ export function getSplashMessages(): string[] {
if (now >= startDate && now <= endDate) {
console.log(`Adding ${messages.length} ${name} splash messages (weight: x${SEASONAL_WEIGHT_MULTIPLIER})`);
messages.forEach((message) => {
for (const message of messages) {
const weightedMessage = Array(SEASONAL_WEIGHT_MULTIPLIER).fill(message);
splashMessages.push(...weightedMessage);
});
}
}
}
}
return splashMessages.map((message) => `splashMessages:${message}`);
return splashMessages.map(message => `splashMessages:${message}`);
}

View File

@ -6,10 +6,10 @@ import i18next from "i18next";
export class Status {
public effect: StatusEffect;
/** Toxic damage is `1/16 max HP * toxicTurnCount` */
public toxicTurnCount: number = 0;
public toxicTurnCount = 0;
public sleepTurnsRemaining?: number;
constructor(effect: StatusEffect, toxicTurnCount: number = 0, sleepTurnsRemaining?: number) {
constructor(effect: StatusEffect, toxicTurnCount = 0, sleepTurnsRemaining?: number) {
this.effect = effect;
this.toxicTurnCount = toxicTurnCount;
this.sleepTurnsRemaining = sleepTurnsRemaining;
@ -23,7 +23,9 @@ export class Status {
}
isPostTurn(): boolean {
return this.effect === StatusEffect.POISON || this.effect === StatusEffect.TOXIC || this.effect === StatusEffect.BURN;
return (
this.effect === StatusEffect.POISON || this.effect === StatusEffect.TOXIC || this.effect === StatusEffect.BURN
);
}
}
@ -46,7 +48,11 @@ function getStatusEffectMessageKey(statusEffect: StatusEffect | undefined): stri
}
}
export function getStatusEffectObtainText(statusEffect: StatusEffect | undefined, pokemonNameWithAffix: string, sourceText?: string): string {
export function getStatusEffectObtainText(
statusEffect: StatusEffect | undefined,
pokemonNameWithAffix: string,
sourceText?: string,
): string {
if (statusEffect === StatusEffect.NONE) {
return "";
}
@ -56,7 +62,10 @@ export function getStatusEffectObtainText(statusEffect: StatusEffect | undefined
return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix });
}
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.obtainSource` as ParseKeys;
return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix, sourceText: sourceText });
return i18next.t(i18nKey, {
pokemonNameWithAffix: pokemonNameWithAffix,
sourceText: sourceText,
});
}
export function getStatusEffectActivationText(statusEffect: StatusEffect, pokemonNameWithAffix: string): string {
@ -142,7 +151,6 @@ export function getRandomStatus(statusA: Status | null, statusB: Status | null):
return statusA;
}
return randIntRange(0, 2) ? statusA : statusB;
}
@ -157,7 +165,7 @@ export function getNonVolatileStatusEffects():Array<StatusEffect> {
StatusEffect.PARALYSIS,
StatusEffect.SLEEP,
StatusEffect.FREEZE,
StatusEffect.BURN
StatusEffect.BURN,
];
}

View File

@ -1,7 +1,7 @@
import type Pokemon from "../field/pokemon";
import type Move from "./move";
import { Type } from "#enums/type";
import { ProtectAttr } from "./move";
import type Move from "./moves/move";
import { PokemonType } from "#enums/pokemon-type";
import { ProtectAttr } from "./moves/move";
import type { BattlerIndex } from "#app/battle";
import i18next from "i18next";
@ -10,7 +10,7 @@ export enum TerrainType {
MISTY,
ELECTRIC,
GRASSY,
PSYCHIC
PSYCHIC,
}
export class Terrain {
@ -30,20 +30,20 @@ export class Terrain {
return true;
}
getAttackTypeMultiplier(attackType: Type): number {
getAttackTypeMultiplier(attackType: PokemonType): number {
switch (this.terrainType) {
case TerrainType.ELECTRIC:
if (attackType === Type.ELECTRIC) {
if (attackType === PokemonType.ELECTRIC) {
return 1.3;
}
break;
case TerrainType.GRASSY:
if (attackType === Type.GRASS) {
if (attackType === PokemonType.GRASS) {
return 1.3;
}
break;
case TerrainType.PSYCHIC:
if (attackType === Type.PSYCHIC) {
if (attackType === PokemonType.PSYCHIC) {
return 1.3;
}
break;
@ -57,7 +57,10 @@ export class Terrain {
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().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded());
return (
move.getPriority(user) > 0 &&
user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded())
);
}
}
@ -80,7 +83,6 @@ export function getTerrainName(terrainType: TerrainType): string {
return "";
}
export function getTerrainColor(terrainType: TerrainType): [number, number, number] {
switch (terrainType) {
case TerrainType.MISTY:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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