Merge branch 'beta' into flee

This commit is contained in:
Dean 2025-03-09 19:06:22 -07:00 committed by GitHub
commit 5acfdede63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
704 changed files with 55741 additions and 38672 deletions

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
name: ESLint name: Biome Code Quality
on: on:
# Trigger the workflow on push or pull request, # 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 - name: Set up Node.js # Step to set up Node.js environment
uses: actions/setup-node@v4 # Use the setup-node action version 4 uses: actions/setup-node@v4 # Use the setup-node action version 4
with: with:
node-version: 20 # Specify Node.js version 20 node-version-file: '.nvmrc'
- name: Install Node.js dependencies # Step to install Node.js dependencies - name: Install Node.js dependencies # Step to install Node.js dependencies
run: npm ci # Use 'npm ci' to install dependencies run: npm ci # Use 'npm ci' to install dependencies
- name: eslint # Step to run linters - name: eslint # Step to run linters
run: npm run eslint-ci 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 - Opaquer
- OrangeRed - OrangeRed
- Sam aka Flashfyre (initial developer, started PokéRogue) - Sam aka Flashfyre (initial developer, started PokéRogue)
- SirzBenjie
- sirzento - sirzento
- SN34KZ - SN34KZ
- Swain aka torranx - Swain aka torranx
- Temp aka Tempo-anon - Temp aka Tempo-anon
- Walker - Walker
- Wlowscha (aka Curbio)
- Xavion - Xavion
## Bug/Issue Managers ## 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! 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 # Contributing
## 🛠️ Development ## 🛠️ 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. 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 ### 💻 Environment Setup
#### Prerequisites #### Prerequisites
- node: 20.13.1 - node: 20.13.1
- npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) - npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
#### Running Locally #### Running Locally
1. Clone the repo and in the root directory run `npm install` 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* - *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` 2. Run `npm run start:dev` to locally run the project in `localhost:8000`
#### Linting #### 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 ### 📚 Documentation
You can find the auto-generated documentation [here](https://pagefaultgames.github.io/pokerogue/main/index.html). 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 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. 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 ### ❔ FAQ
**How do I test a new _______?** **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 - 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?** **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 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. - 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 ## 🪧 To Do
Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us! Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us!
# 📝 Credits # 📝 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). > 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). 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") { if (typeAnswer.selectedOption === "EXIT") {
console.log("Exiting..."); console.log("Exiting...");
return process.exit(); return process.exit();
} else if (!typeChoices.includes(typeAnswer.selectedOption)) { }
if (!typeChoices.includes(typeAnswer.selectedOption)) {
console.error(`Please provide a valid type (${typeChoices.join(", ")})!`); console.error(`Please provide a valid type (${typeChoices.join(", ")})!`);
return await promptTestType(); return await promptTestType();
} }
@ -74,11 +75,11 @@ async function runInteractive() {
const fileName = fileNameAnswer.userInput const fileName = fileNameAnswer.userInput
.replace(/-+/g, "_") // Convert kebab-case (dashes) to underscores .replace(/-+/g, "_") // Convert kebab-case (dashes) to underscores
.replace(/([a-z])([A-Z])/g, "$1_$2") // Convert camelCase to snake_case .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 .toLowerCase(); // Ensure all lowercase
// Format the description for the test case // 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 // Determine the directory based on the type
let dir; let dir;
let description; let description;

View File

@ -1,7 +1,7 @@
import tseslint from '@typescript-eslint/eslint-plugin'; import tseslint from "@typescript-eslint/eslint-plugin";
import stylisticTs from '@stylistic/eslint-plugin-ts'; import stylisticTs from "@stylistic/eslint-plugin-ts";
import parser from '@typescript-eslint/parser'; import parser from "@typescript-eslint/parser";
import importX from 'eslint-plugin-import-x'; import importX from "eslint-plugin-import-x";
export default [ export default [
{ {
@ -9,46 +9,19 @@ export default [
files: ["src/**/*.{ts,tsx,js,jsx}", "test/**/*.{ts,tsx,js,jsx}"], files: ["src/**/*.{ts,tsx,js,jsx}", "test/**/*.{ts,tsx,js,jsx}"],
ignores: ["dist/*", "build/*", "coverage/*", "public/*", ".github/*", "node_modules/*", ".vscode/*"], ignores: ["dist/*", "build/*", "coverage/*", "public/*", ".github/*", "node_modules/*", ".vscode/*"],
languageOptions: { languageOptions: {
parser: parser parser: parser,
}, },
plugins: { plugins: {
"import-x": importX, "import-x": importX,
'@stylistic/ts': stylisticTs, "@stylistic/ts": stylisticTs,
'@typescript-eslint': tseslint "@typescript-eslint": tseslint,
}, },
rules: { 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 "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) "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 "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 "import-x/extensions": ["error", "never", { json: "always" }], // Enforces no extension for imports unless json
"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
}
}, },
{ {
name: "eslint-tests", name: "eslint-tests",
@ -56,15 +29,15 @@ export default [
languageOptions: { languageOptions: {
parser: parser, parser: parser,
parserOptions: { parserOptions: {
"project": ["./tsconfig.json"] project: ["./tsconfig.json"],
} },
}, },
plugins: { plugins: {
"@typescript-eslint": tseslint "@typescript-eslint": tseslint,
}, },
rules: { 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-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/ "@typescript-eslint/no-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/
} },
} },
] ];

106
index.css
View File

@ -49,16 +49,17 @@ body {
@media (pointer: coarse) { @media (pointer: coarse) {
/* hasTouchscreen() && !isTouchControlsEnabled */ /* hasTouchscreen() && !isTouchControlsEnabled */
body:has(> #touchControls[class=visible]) #app { body:has(> #touchControls[class="visible"]) #app {
align-items: start; 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; transform-origin: top !important;
} }
} }
#layout:fullscreen #dpad, #layout:fullscreen { #layout:fullscreen #dpad,
#layout:fullscreen {
bottom: 6rem; bottom: 6rem;
} }
@ -76,7 +77,6 @@ input {
display: none !important; display: none !important;
} }
input:-internal-autofill-selected { input:-internal-autofill-selected {
-webkit-background-clip: text; -webkit-background-clip: text;
background-clip: text; background-clip: text;
@ -91,18 +91,33 @@ input:-internal-autofill-selected {
--controls-padding: 1rem; --controls-padding: 1rem;
--controls-size-with-padding: calc(var(--controls-size) + var(--controls-padding)); --controls-size-with-padding: calc(
--controls-size-with-wide-padding: calc(var(--controls-size) *1.2 + var(--controls-padding)); 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-size: calc(var(--controls-size) * 0.8);
--control-group-extra-wide-size: calc(var(--controls-size) * 1.2); --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-2-offset: calc(
--control-group-extra-1-offset: calc(var(--controls-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2); 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); --small-control-size: calc(var(--controls-size) / 3);
--rect-control-size: calc(var(--controls-size) * 0.74); --rect-control-size: calc(var(--controls-size) * 0.74);
font-family: 'emerald'; font-family: "emerald";
font-size: var(--controls-size); font-size: var(--controls-size);
text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size); text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size);
color: var(--color-light); color: var(--color-light);
@ -146,31 +161,68 @@ input:-internal-autofill-selected {
/* Hide buttons on specific UIs */ /* Hide buttons on specific UIs */
/* Show #apadPreviousTab and #apadNextTab only in settings, except in touch configuration panel */ /* 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"]) #apadPreviousTab,
#touchControls:not([data-ui-mode^='SETTINGS']) #apadNextTab, #touchControls:not([data-ui-mode^="SETTINGS"]) #apadNextTab,
#touchControls:is(.config-mode) #apadPreviousTab, #touchControls:is(.config-mode) #apadPreviousTab,
#touchControls:is(.config-mode) #apadNextTab { #touchControls:is(.config-mode) #apadNextTab {
display: none; display: none;
} }
/* Show #apadInfo only in battle */ /* 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; display: none;
} }
/* Show #apadStats only in battle and shop */ /* 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; display: none;
} }
/* Show cycle buttons only on STARTER_SELECT and on touch configuration panel */ /* 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(
#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, [data-ui-mode="STARTER_SELECT"],
#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, [data-ui-mode="POKEDEX"],
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleNature, [data-ui-mode="POKEDEX_PAGE"]
#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, #apadOpenFilters,
#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"],
[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; display: none;
} }
@ -217,16 +269,18 @@ input:-internal-autofill-selected {
font-size: var(--small-control-size); font-size: var(--small-control-size);
border-radius: 8px; border-radius: 8px;
padding: 2px 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 { #configToolbar .button:active {
opacity: var(--touch-control-opacity) opacity: var(--touch-control-opacity);
} }
#configToolbar .orientation-label { #configToolbar .orientation-label {
font-size: var(--small-control-size); 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 */ /* dpad */
@ -270,7 +324,8 @@ input:-internal-autofill-selected {
.apad-small > .apad-label { .apad-small > .apad-label {
font-size: var(--small-control-size); 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 { .apad-rectangle {
@ -320,7 +375,8 @@ input:-internal-autofill-selected {
/* Layout */ /* Layout */
#layout:fullscreen #dpad, #layout:fullscreen #apad { #layout:fullscreen #dpad,
#layout:fullscreen #apad {
bottom: 6rem; bottom: 6rem;
} }

View File

@ -1,9 +1,9 @@
pre-commit: pre-commit:
parallel: true parallel: true
commands: commands:
eslint: biome-lint:
glob: "*.{js,jsx,ts,tsx}" 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 stage_fixed: true
skip: skip:
- merge - merge
@ -11,9 +11,9 @@ pre-commit:
pre-push: pre-push:
commands: commands:
eslint: biome-lint:
glob: "*.{js,ts,jsx,tsx}" 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: post-merge:
commands: commands:

165
package-lock.json generated
View File

@ -21,6 +21,7 @@
"phaser3-rex-plugins": "^1.1.84" "phaser3-rex-plugins": "^1.1.84"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4",
"@eslint/js": "^9.3.0", "@eslint/js": "^9.3.0",
"@hpcc-js/wasm": "^2.18.0", "@hpcc-js/wasm": "^2.18.0",
"@stylistic/eslint-plugin-ts": "^2.6.0-beta.0", "@stylistic/eslint-plugin-ts": "^2.6.0-beta.0",
@ -492,6 +493,170 @@
"node": ">=6.9.0" "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": { "node_modules/@bundled-es-modules/cookie": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz", "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz",

View File

@ -16,6 +16,8 @@
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"eslint": "eslint --fix .", "eslint": "eslint --fix .",
"eslint-ci": "eslint .", "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", "docs": "typedoc",
"depcruise": "depcruise src", "depcruise": "depcruise src",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg", "depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
@ -26,6 +28,7 @@
"update-locales:remote": "git submodule update --progress --init --recursive --force --remote" "update-locales:remote": "git submodule update --progress --init --recursive --force --remote"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4",
"@eslint/js": "^9.3.0", "@eslint/js": "^9.3.0",
"@hpcc-js/wasm": "^2.18.0", "@hpcc-js/wasm": "^2.18.0",
"@stylistic/eslint-plugin-ts": "^2.6.0-beta.0", "@stylistic/eslint-plugin-ts": "^2.6.0-beta.0",

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -50,7 +50,7 @@ export enum BattleType {
WILD, WILD,
TRAINER, TRAINER,
CLEAR, CLEAR,
MYSTERY_ENCOUNTER MYSTERY_ENCOUNTER,
} }
export enum BattlerIndex { export enum BattlerIndex {
@ -58,7 +58,7 @@ export enum BattlerIndex {
PLAYER, PLAYER,
PLAYER_2, PLAYER_2,
ENEMY, ENEMY,
ENEMY_2 ENEMY_2,
} }
export interface TurnCommand { export interface TurnCommand {
@ -71,12 +71,12 @@ export interface TurnCommand {
} }
export interface FaintLogEntry { export interface FaintLogEntry {
pokemon: Pokemon, pokemon: Pokemon;
turn: number turn: number;
} }
interface TurnCommands { interface TurnCommands {
[key: number]: TurnCommand | null [key: number]: TurnCommand | null;
} }
export default class Battle { export default class Battle {
@ -89,19 +89,19 @@ export default class Battle {
public enemyParty: EnemyPokemon[] = []; public enemyParty: EnemyPokemon[] = [];
public seenEnemyPartyMemberIds: Set<number> = new Set<number>(); public seenEnemyPartyMemberIds: Set<number> = new Set<number>();
public double: boolean; public double: boolean;
public started: boolean = false; public started = false;
public enemySwitchCounter: number = 0; public enemySwitchCounter = 0;
public turn: number = 0; public turn = 0;
public preTurnCommands: TurnCommands; public preTurnCommands: TurnCommands;
public turnCommands: TurnCommands; public turnCommands: TurnCommands;
public playerParticipantIds: Set<number> = new Set<number>(); public playerParticipantIds: Set<number> = new Set<number>();
public battleScore: number = 0; public battleScore = 0;
public postBattleLoot: PokemonHeldItemModifier[] = []; public postBattleLoot: PokemonHeldItemModifier[] = [];
public escapeAttempts: number = 0; public escapeAttempts = 0;
public lastMove: Moves; public lastMove: Moves;
public battleSeed: string = Utils.randomString(16, true); public battleSeed: string = Utils.randomString(16, true);
private battleSeedState: string | null = null; 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 */ /** Primarily for double battles, keeps track of last enemy and player pokemon that triggered its ability or used a move */
public lastEnemyInvolved: number; public lastEnemyInvolved: number;
public lastPlayerInvolved: number; public lastPlayerInvolved: number;
@ -111,7 +111,7 @@ export default class Battle {
* This is saved here since we encounter a new enemy every wave. * 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). * {@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 playerFaintsHistory: FaintLogEntry[] = [];
public enemyFaintsHistory: 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 */ /** If the current battle is a Mystery Encounter, this will always be defined */
public mysteryEncounter?: MysteryEncounter; 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.gameMode = gameMode;
this.waveIndex = waveIndex; this.waveIndex = waveIndex;
this.battleType = battleType; this.battleType = battleType;
this.trainer = trainer ?? null; this.trainer = trainer ?? null;
this.initBattleSpec(); this.initBattleSpec();
this.enemyLevels = battleType !== BattleType.TRAINER this.enemyLevels =
battleType !== BattleType.TRAINER
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave()) ? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
: trainer?.getPartyLevels(this.waveIndex); : trainer?.getPartyLevels(this.waveIndex);
this.double = double; this.double = double;
@ -194,12 +195,19 @@ export default class Battle {
} }
addPostBattleLoot(enemyPokemon: EnemyPokemon): void { 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; const ret = i as PokemonHeldItemModifier;
//@ts-ignore - this is awful to fix/change //@ts-ignore - this is awful to fix/change
ret.pokemonId = null; ret.pokemonId = null;
return ret; return ret;
})); }),
);
} }
pickUpScatteredMoney(): void { pickUpScatteredMoney(): void {
@ -214,7 +222,9 @@ export default class Battle {
const userLocale = navigator.language || "en-US"; const userLocale = navigator.language || "en-US";
const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale); 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.queueMessage(message, undefined, true);
globalScene.currentBattle.moneyScattered = 0; globalScene.currentBattle.moneyScattered = 0;
@ -227,13 +237,17 @@ export default class Battle {
} }
for (const p of globalScene.getEnemyParty()) { for (const p of globalScene.getEnemyParty()) {
if (p.isBoss()) { 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); const finalBattleScore = Math.ceil(this.battleScore * turnMultiplier);
globalScene.score += finalBattleScore; 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}`); console.log(`Total Score: ${globalScene.score}`);
globalScene.updateScoreText(); globalScene.updateScoreText();
} }
@ -243,16 +257,20 @@ export default class Battle {
// Music is overridden for MEs during ME onInit() // Music is overridden for MEs during ME onInit()
// Should not use any BGM overrides before swapping from DEFAULT mode // Should not use any BGM overrides before swapping from DEFAULT mode
return null; 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) { if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) {
return `encounter_${this.trainer?.getEncounterBgm()}`; return `encounter_${this.trainer?.getEncounterBgm()}`;
} }
if (globalScene.musicPreference === MusicPreference.GENFIVE) { if (globalScene.musicPreference === MusicPreference.GENFIVE) {
return this.trainer?.getBattleBgm() ?? null; return this.trainer?.getBattleBgm() ?? null;
} else { }
return this.trainer?.getMixedBattleBgm() ?? null; 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"; return "end_summit";
} }
const wildOpponents = globalScene.getEnemyParty(); const wildOpponents = globalScene.getEnemyParty();
@ -281,7 +299,8 @@ export default class Battle {
} }
return "battle_legendary_unova"; return "battle_legendary_unova";
} }
} else if (globalScene.musicPreference === MusicPreference.ALLGENS) { }
if (globalScene.musicPreference === MusicPreference.ALLGENS) {
switch (pokemon.species.speciesId) { switch (pokemon.species.speciesId) {
case Species.ARTICUNO: case Species.ARTICUNO:
case Species.ZAPDOS: case Species.ZAPDOS:
@ -434,7 +453,7 @@ export default class Battle {
* @param min The minimum integer to pick, default `0` * @param min The minimum integer to pick, default `0`
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) * @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) { if (range <= 1) {
return min; return min;
} }
@ -467,7 +486,13 @@ export default class Battle {
export class FixedBattle extends Battle { export class FixedBattle extends Battle {
constructor(waveIndex: number, config: FixedBattleConfig) { 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) { if (config.getEnemyParty) {
this.enemyParty = 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 * 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 * @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 * @param seedOffset the seed offset to use for the random generation of the trainer
* @returns the generated 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 () => { return () => {
const rand = Utils.randSeedInt(trainerPool.length); const rand = Utils.randSeedInt(trainerPool.length);
const trainerTypes: TrainerType[] = []; const trainerTypes: TrainerType[] = [];
globalScene.executeWithSeedOffset(() => { globalScene.executeWithSeedOffset(() => {
for (const trainerPoolEntry of trainerPool) { for (const trainerPoolEntry of trainerPool) {
const trainerType = Array.isArray(trainerPoolEntry) const trainerType = Array.isArray(trainerPoolEntry) ? Utils.randSeedItem(trainerPoolEntry) : trainerPoolEntry;
? Utils.randSeedItem(trainerPoolEntry)
: trainerPoolEntry;
trainerTypes.push(trainerType); trainerTypes.push(trainerType);
} }
}, seedOffset); }, seedOffset);
let trainerGender = TrainerVariant.DEFAULT; let trainerGender = TrainerVariant.DEFAULT;
if (randomGender) { 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 */ /* 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]); const isEvilTeamGrunt = evilTeamGrunts.includes(trainerTypes[rand]);
if (trainerConfigs[trainerTypes[rand]].hasDouble && isEvilTeamGrunt) { 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); return new Trainer(trainerTypes[rand], trainerGender);
@ -556,7 +593,7 @@ export function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[
} }
export interface FixedBattleConfigs { export interface FixedBattleConfigs {
[key: number]: FixedBattleConfig [key: number]: FixedBattleConfig;
} }
/** /**
* Youngster/Lass on 5 * Youngster/Lass on 5
@ -568,51 +605,355 @@ export interface FixedBattleConfigs {
* Champion on 190 * Champion on 190
*/ */
export const classicFixedBattles: FixedBattleConfigs = { export const classicFixedBattles: FixedBattleConfigs = {
[ClassicFixedBossWaves.TOWN_YOUNGSTER]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) [ClassicFixedBossWaves.TOWN_YOUNGSTER]: new FixedBattleConfig()
.setGetTrainerFunc(() => new Trainer(TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), .setBattleType(BattleType.TRAINER)
[ClassicFixedBossWaves.RIVAL_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), () => new Trainer(TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? 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)) [ClassicFixedBossWaves.RIVAL_1]: new FixedBattleConfig()
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }), .setBattleType(BattleType.TRAINER)
[ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(
.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) new Trainer(
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_3, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) TrainerType.RIVAL,
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }), globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
[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) [ClassicFixedBossWaves.RIVAL_2]: new FixedBattleConfig()
.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)), .setBattleType(BattleType.TRAINER)
[ClassicFixedBossWaves.EVIL_ADMIN_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1) .setGetTrainerFunc(
.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) new Trainer(
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_4, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) TrainerType.RIVAL_2,
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }), globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
[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) .setCustomModifierRewards({
.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)), guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
[ClassicFixedBossWaves.EVIL_BOSS_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1) allowLuckUpgrades: false,
.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.EVIL_GRUNT_1]: new FixedBattleConfig()
[ClassicFixedBossWaves.RIVAL_5]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_5, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) .setGetTrainerFunc(
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }), getRandomTrainerFunc(
[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 ])) TrainerType.ROCKET_GRUNT,
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }), TrainerType.MAGMA_GRUNT,
[ClassicFixedBossWaves.ELITE_FOUR_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) TrainerType.AQUA_GRUNT,
.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 ])), TrainerType.GALACTIC_GRUNT,
[ClassicFixedBossWaves.ELITE_FOUR_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1) TrainerType.PLASMA_GRUNT,
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY, TrainerType.AMARYS ])), TrainerType.FLARE_GRUNT,
[ClassicFixedBossWaves.ELITE_FOUR_3]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1) TrainerType.AETHER_GRUNT,
.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 ])), TrainerType.SKULL_GRUNT,
[ClassicFixedBossWaves.ELITE_FOUR_4]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1) TrainerType.MACRO_GRUNT,
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL, TrainerType.DRAYTON ])), TrainerType.STAR_GRUNT,
[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 ])), true,
[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.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_RIGHT_BRACKET: Phaser.Input.Keyboard.KeyCodes.CLOSED_BRACKET,
KEY_SEMICOLON: Phaser.Input.Keyboard.KeyCodes.SEMICOLON, KEY_SEMICOLON: Phaser.Input.Keyboard.KeyCodes.SEMICOLON,
KEY_BACKSPACE: Phaser.Input.Keyboard.KeyCodes.BACKSPACE, KEY_BACKSPACE: Phaser.Input.Keyboard.KeyCodes.BACKSPACE,
KEY_ALT: Phaser.Input.Keyboard.KeyCodes.ALT KEY_ALT: Phaser.Input.Keyboard.KeyCodes.ALT,
}, },
icons: { icons: {
KEY_A: "A.png", KEY_A: "A.png",
@ -131,7 +131,6 @@ const cfg_keyboard_qwerty = {
KEY_F11: "F11.png", KEY_F11: "F11.png",
KEY_F12: "F12.png", KEY_F12: "F12.png",
KEY_PAGE_DOWN: "PAGE_DOWN.png", KEY_PAGE_DOWN: "PAGE_DOWN.png",
KEY_PAGE_UP: "PAGE_UP.png", KEY_PAGE_UP: "PAGE_UP.png",
@ -163,7 +162,7 @@ const cfg_keyboard_qwerty = {
KEY_SEMICOLON: "SEMICOLON.png", KEY_SEMICOLON: "SEMICOLON.png",
KEY_BACKSPACE: "BACK.png", KEY_BACKSPACE: "BACK.png",
KEY_ALT: "ALT.png" KEY_ALT: "ALT.png",
}, },
settings: { settings: {
[SettingKeyboard.Button_Up]: Button.UP, [SettingKeyboard.Button_Up]: Button.UP,
@ -274,7 +273,7 @@ const cfg_keyboard_qwerty = {
KEY_LEFT_BRACKET: -1, KEY_LEFT_BRACKET: -1,
KEY_RIGHT_BRACKET: -1, KEY_RIGHT_BRACKET: -1,
KEY_SEMICOLON: -1, KEY_SEMICOLON: -1,
KEY_ALT: -1 KEY_ALT: -1,
}, },
blacklist: [ blacklist: [
"KEY_ENTER", "KEY_ENTER",
@ -287,7 +286,7 @@ const cfg_keyboard_qwerty = {
"KEY_ARROW_RIGHT", "KEY_ARROW_RIGHT",
"KEY_DEL", "KEY_DEL",
"KEY_HOME", "KEY_HOME",
] ],
}; };
export default cfg_keyboard_qwerty; export default cfg_keyboard_qwerty;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -159,7 +159,7 @@ export abstract class AbAttr {
public showAbility: boolean; public showAbility: boolean;
private extraCondition: AbAttrCondition; private extraCondition: AbAttrCondition;
constructor(showAbility: boolean = true) { constructor(showAbility = true) {
this.showAbility = showAbility; this.showAbility = showAbility;
} }
@ -775,7 +775,7 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr {
private selfTarget: boolean; private selfTarget: boolean;
private allOthers: boolean; private allOthers: boolean;
constructor(condition: PokemonDefendCondition, stat: BattleStat, stages: number, selfTarget: boolean = true, allOthers: boolean = false) { constructor(condition: PokemonDefendCondition, stat: BattleStat, stages: number, selfTarget = true, allOthers = false) {
super(true); super(true);
this.condition = condition; this.condition = condition;
@ -813,7 +813,7 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
private stages: number; private stages: number;
private selfTarget: boolean; private selfTarget: boolean;
constructor(condition: PokemonDefendCondition, hpGate: number, stats: BattleStat[], stages: number, selfTarget: boolean = true) { constructor(condition: PokemonDefendCondition, hpGate: number, stats: BattleStat[], stages: number, selfTarget = true) {
super(true); super(true);
this.condition = condition; this.condition = condition;
@ -1320,7 +1320,7 @@ export class FieldMultiplyStatAbAttr extends AbAttr {
private multiplier: number; private multiplier: number;
private canStack: boolean; private canStack: boolean;
constructor(stat: Stat, multiplier: number, canStack: boolean = false) { constructor(stat: Stat, multiplier: number, canStack = false) {
super(false); super(false);
this.stat = stat; this.stat = stat;
@ -1510,7 +1510,7 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr {
private condition: PokemonAttackCondition; private condition: PokemonAttackCondition;
private powerMultiplier: number; private powerMultiplier: number;
constructor(condition: PokemonAttackCondition, powerMultiplier: number, showAbility: boolean = true) { constructor(condition: PokemonAttackCondition, powerMultiplier: number, showAbility = true) {
super(showAbility); super(showAbility);
this.condition = condition; this.condition = condition;
this.powerMultiplier = powerMultiplier; this.powerMultiplier = powerMultiplier;
@ -1555,7 +1555,7 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr {
* @param mult A function which takes the user, target, and move, and returns the power multiplier. 1 means no multiplier. * @param mult A function which takes the user, target, and move, and returns the power multiplier. 1 means no multiplier.
* @param {boolean} showAbility Whether to show the ability when it activates. * @param {boolean} showAbility Whether to show the ability when it activates.
*/ */
constructor(mult: (user: Pokemon, target: Pokemon, move: Move) => number, showAbility: boolean = true) { constructor(mult: (user: Pokemon, target: Pokemon, move: Move) => number, showAbility = true) {
super(showAbility); super(showAbility);
this.mult = mult; this.mult = mult;
} }
@ -1678,7 +1678,7 @@ export class PostAttackAbAttr extends AbAttr {
private attackCondition: PokemonAttackCondition; private attackCondition: PokemonAttackCondition;
/** The default attackCondition requires that the selected move is a damaging move */ /** The default attackCondition requires that the selected move is a damaging move */
constructor(attackCondition: PokemonAttackCondition = (user, target, move) => (move.category !== MoveCategory.STATUS), showAbility: boolean = true) { constructor(attackCondition: PokemonAttackCondition = (user, target, move) => (move.category !== MoveCategory.STATUS), showAbility = true) {
super(showAbility); super(showAbility);
this.attackCondition = attackCondition; this.attackCondition = attackCondition;
@ -2151,7 +2151,7 @@ export class PostSummonAbAttr extends AbAttr {
/** Should the ability activate when gained in battle? This will almost always be true */ /** Should the ability activate when gained in battle? This will almost always be true */
private activateOnGain: boolean; private activateOnGain: boolean;
constructor(showAbility: boolean = true, activateOnGain: boolean = true) { constructor(showAbility = true, activateOnGain = true) {
super(showAbility); super(showAbility);
this.activateOnGain = activateOnGain; this.activateOnGain = activateOnGain;
} }
@ -2334,7 +2334,7 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr {
private healRatio: number; private healRatio: number;
private showAnim: boolean; private showAnim: boolean;
constructor(healRatio: number, showAnim: boolean = false) { constructor(healRatio: number, showAnim = false) {
super(); super();
this.healRatio = healRatio || 4; this.healRatio = healRatio || 4;
@ -3296,7 +3296,7 @@ export class MultCritAbAttr extends AbAttr {
export class ConditionalCritAbAttr extends AbAttr { export class ConditionalCritAbAttr extends AbAttr {
private condition: PokemonAttackCondition; private condition: PokemonAttackCondition;
constructor(condition: PokemonAttackCondition, checkUser?: Boolean) { constructor(condition: PokemonAttackCondition, checkUser?: boolean) {
super(); super();
this.condition = condition; this.condition = condition;
@ -3400,7 +3400,7 @@ export class IgnoreContactAbAttr extends AbAttr { }
export class PreWeatherEffectAbAttr extends AbAttr { export class PreWeatherEffectAbAttr extends AbAttr {
applyPreWeatherEffect( applyPreWeatherEffect(
pokemon: Pokemon, pokemon: Pokemon,
passive: Boolean, passive: boolean,
simulated: boolean, simulated: boolean,
weather: Weather | null, weather: Weather | null,
cancelled: Utils.BooleanHolder, cancelled: Utils.BooleanHolder,
@ -3837,7 +3837,7 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr {
private allyTarget: boolean; private allyTarget: boolean;
private target: Pokemon; private target: Pokemon;
constructor(allyTarget: boolean = false) { constructor(allyTarget = false) {
super(true); super(true);
this.allyTarget = allyTarget; this.allyTarget = allyTarget;
} }
@ -4049,7 +4049,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {
* @returns `true` if any opponents are sleeping * @returns `true` if any opponents are sleeping
*/ */
override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
let hadEffect: boolean = false; let hadEffect = false;
for (const opp of pokemon.getOpponents()) { for (const opp of pokemon.getOpponents()) {
if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) {
if (!simulated) { if (!simulated) {
@ -5150,9 +5150,9 @@ function applySingleAbAttrs<TAttr extends AbAttr>(
attrType: Constructor<TAttr>, attrType: Constructor<TAttr>,
applyFunc: AbAttrApplyFunc<TAttr>, applyFunc: AbAttrApplyFunc<TAttr>,
args: any[], args: any[],
gainedMidTurn: boolean = false, gainedMidTurn = false,
simulated: boolean = false, simulated = false,
showAbilityInstant: boolean = false, showAbilityInstant = false,
messages: string[] = [] messages: string[] = []
) { ) {
if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) {
@ -5362,7 +5362,7 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr {
private helper: ForceSwitchOutHelper = new ForceSwitchOutHelper(SwitchType.SWITCH); private helper: ForceSwitchOutHelper = new ForceSwitchOutHelper(SwitchType.SWITCH);
private hpRatio: number; private hpRatio: number;
constructor(hpRatio: number = 0.5) { constructor(hpRatio = 0.5) {
super(); super();
this.hpRatio = hpRatio; this.hpRatio = hpRatio;
} }
@ -5446,10 +5446,10 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(
pokemon: Pokemon | null, pokemon: Pokemon | null,
applyFunc: AbAttrApplyFunc<TAttr>, applyFunc: AbAttrApplyFunc<TAttr>,
args: any[], args: any[],
showAbilityInstant: boolean = false, showAbilityInstant = false,
simulated: boolean = false, simulated = false,
messages: string[] = [], messages: string[] = [],
gainedMidTurn: boolean = false gainedMidTurn = false
) { ) {
for (const passive of [ false, true ]) { for (const passive of [ false, true ]) {
if (pokemon) { if (pokemon) {
@ -5463,7 +5463,7 @@ export function applyAbAttrs(
attrType: Constructor<AbAttr>, attrType: Constructor<AbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
cancelled: Utils.BooleanHolder | null, cancelled: Utils.BooleanHolder | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<AbAttr>( applyAbAttrsInternal<AbAttr>(
@ -5479,7 +5479,7 @@ export function applyAbAttrs(
export function applyPostBattleInitAbAttrs( export function applyPostBattleInitAbAttrs(
attrType: Constructor<PostBattleInitAbAttr>, attrType: Constructor<PostBattleInitAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostBattleInitAbAttr>( applyAbAttrsInternal<PostBattleInitAbAttr>(
@ -5498,7 +5498,7 @@ export function applyPreDefendAbAttrs(
attacker: Pokemon, attacker: Pokemon,
move: Move | null, move: Move | null,
cancelled: Utils.BooleanHolder | null, cancelled: Utils.BooleanHolder | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreDefendAbAttr>( applyAbAttrsInternal<PreDefendAbAttr>(
@ -5517,7 +5517,7 @@ export function applyPostDefendAbAttrs(
attacker: Pokemon, attacker: Pokemon,
move: Move, move: Move,
hitResult: HitResult | null, hitResult: HitResult | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostDefendAbAttr>( applyAbAttrsInternal<PostDefendAbAttr>(
@ -5536,7 +5536,7 @@ export function applyPostMoveUsedAbAttrs(
move: PokemonMove, move: PokemonMove,
source: Pokemon, source: Pokemon,
targets: BattlerIndex[], targets: BattlerIndex[],
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostMoveUsedAbAttr>( applyAbAttrsInternal<PostMoveUsedAbAttr>(
@ -5554,7 +5554,7 @@ export function applyStatMultiplierAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
stat: BattleStat, stat: BattleStat,
statValue: Utils.NumberHolder, statValue: Utils.NumberHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<StatMultiplierAbAttr>( applyAbAttrsInternal<StatMultiplierAbAttr>(
@ -5569,7 +5569,7 @@ export function applyPostSetStatusAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
effect: StatusEffect, effect: StatusEffect,
sourcePokemon?: Pokemon | null, sourcePokemon?: Pokemon | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostSetStatusAbAttr>( applyAbAttrsInternal<PostSetStatusAbAttr>(
@ -5587,7 +5587,7 @@ export function applyPostDamageAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
damage: number, damage: number,
passive: boolean, passive: boolean,
simulated: boolean = false, simulated = false,
args: any[], args: any[],
source?: Pokemon, source?: Pokemon,
): void { ): void {
@ -5616,7 +5616,7 @@ export function applyFieldStatMultiplierAbAttrs(
statValue: Utils.NumberHolder, statValue: Utils.NumberHolder,
checkedPokemon: Pokemon, checkedPokemon: Pokemon,
hasApplied: Utils.BooleanHolder, hasApplied: Utils.BooleanHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<FieldMultiplyStatAbAttr>( applyAbAttrsInternal<FieldMultiplyStatAbAttr>(
@ -5633,7 +5633,7 @@ export function applyPreAttackAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
defender: Pokemon | null, defender: Pokemon | null,
move: Move, move: Move,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreAttackAbAttr>( applyAbAttrsInternal<PreAttackAbAttr>(
@ -5652,7 +5652,7 @@ export function applyPostAttackAbAttrs(
defender: Pokemon, defender: Pokemon,
move: Move, move: Move,
hitResult: HitResult | null, hitResult: HitResult | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostAttackAbAttr>( applyAbAttrsInternal<PostAttackAbAttr>(
@ -5669,7 +5669,7 @@ export function applyPostKnockOutAbAttrs(
attrType: Constructor<PostKnockOutAbAttr>, attrType: Constructor<PostKnockOutAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
knockedOut: Pokemon, knockedOut: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostKnockOutAbAttr>( applyAbAttrsInternal<PostKnockOutAbAttr>(
@ -5685,7 +5685,7 @@ export function applyPostKnockOutAbAttrs(
export function applyPostVictoryAbAttrs( export function applyPostVictoryAbAttrs(
attrType: Constructor<PostVictoryAbAttr>, attrType: Constructor<PostVictoryAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostVictoryAbAttr>( applyAbAttrsInternal<PostVictoryAbAttr>(
@ -5701,7 +5701,7 @@ export function applyPostVictoryAbAttrs(
export function applyPostSummonAbAttrs( export function applyPostSummonAbAttrs(
attrType: Constructor<PostSummonAbAttr>, attrType: Constructor<PostSummonAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostSummonAbAttr>( applyAbAttrsInternal<PostSummonAbAttr>(
@ -5717,7 +5717,7 @@ export function applyPostSummonAbAttrs(
export function applyPreSwitchOutAbAttrs( export function applyPreSwitchOutAbAttrs(
attrType: Constructor<PreSwitchOutAbAttr>, attrType: Constructor<PreSwitchOutAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreSwitchOutAbAttr>( applyAbAttrsInternal<PreSwitchOutAbAttr>(
@ -5733,7 +5733,7 @@ export function applyPreSwitchOutAbAttrs(
export function applyPreLeaveFieldAbAttrs( export function applyPreLeaveFieldAbAttrs(
attrType: Constructor<PreLeaveFieldAbAttr>, attrType: Constructor<PreLeaveFieldAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
return applyAbAttrsInternal<PreLeaveFieldAbAttr>( return applyAbAttrsInternal<PreLeaveFieldAbAttr>(
@ -5752,7 +5752,7 @@ export function applyPreStatStageChangeAbAttrs(
pokemon: Pokemon | null, pokemon: Pokemon | null,
stat: BattleStat, stat: BattleStat,
cancelled: Utils.BooleanHolder, cancelled: Utils.BooleanHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreStatStageChangeAbAttr>( applyAbAttrsInternal<PreStatStageChangeAbAttr>(
@ -5771,7 +5771,7 @@ export function applyPostStatStageChangeAbAttrs(
stats: BattleStat[], stats: BattleStat[],
stages: integer, stages: integer,
selfTarget: boolean, selfTarget: boolean,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostStatStageChangeAbAttr>( applyAbAttrsInternal<PostStatStageChangeAbAttr>(
@ -5789,7 +5789,7 @@ export function applyPreSetStatusAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
effect: StatusEffect | undefined, effect: StatusEffect | undefined,
cancelled: Utils.BooleanHolder, cancelled: Utils.BooleanHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreSetStatusAbAttr>( applyAbAttrsInternal<PreSetStatusAbAttr>(
@ -5807,7 +5807,7 @@ export function applyPreApplyBattlerTagAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
tag: BattlerTag, tag: BattlerTag,
cancelled: Utils.BooleanHolder, cancelled: Utils.BooleanHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreApplyBattlerTagAbAttr>( applyAbAttrsInternal<PreApplyBattlerTagAbAttr>(
@ -5825,7 +5825,7 @@ export function applyPreWeatherEffectAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
weather: Weather | null, weather: Weather | null,
cancelled: Utils.BooleanHolder, cancelled: Utils.BooleanHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreWeatherDamageAbAttr>( applyAbAttrsInternal<PreWeatherDamageAbAttr>(
@ -5841,7 +5841,7 @@ export function applyPreWeatherEffectAbAttrs(
export function applyPostTurnAbAttrs( export function applyPostTurnAbAttrs(
attrType: Constructor<PostTurnAbAttr>, attrType: Constructor<PostTurnAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostTurnAbAttr>( applyAbAttrsInternal<PostTurnAbAttr>(
@ -5858,7 +5858,7 @@ export function applyPostWeatherChangeAbAttrs(
attrType: Constructor<PostWeatherChangeAbAttr>, attrType: Constructor<PostWeatherChangeAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
weather: WeatherType, weather: WeatherType,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostWeatherChangeAbAttr>( applyAbAttrsInternal<PostWeatherChangeAbAttr>(
@ -5875,7 +5875,7 @@ export function applyPostWeatherLapseAbAttrs(
attrType: Constructor<PostWeatherLapseAbAttr>, attrType: Constructor<PostWeatherLapseAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
weather: Weather | null, weather: Weather | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostWeatherLapseAbAttr>( applyAbAttrsInternal<PostWeatherLapseAbAttr>(
@ -5892,7 +5892,7 @@ export function applyPostTerrainChangeAbAttrs(
attrType: Constructor<PostTerrainChangeAbAttr>, attrType: Constructor<PostTerrainChangeAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
terrain: TerrainType, terrain: TerrainType,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostTerrainChangeAbAttr>( applyAbAttrsInternal<PostTerrainChangeAbAttr>(
@ -5911,7 +5911,7 @@ export function applyCheckTrappedAbAttrs(
trapped: Utils.BooleanHolder, trapped: Utils.BooleanHolder,
otherPokemon: Pokemon, otherPokemon: Pokemon,
messages: string[], messages: string[],
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<CheckTrappedAbAttr>( applyAbAttrsInternal<CheckTrappedAbAttr>(
@ -5928,7 +5928,7 @@ export function applyCheckTrappedAbAttrs(
export function applyPostBattleAbAttrs( export function applyPostBattleAbAttrs(
attrType: Constructor<PostBattleAbAttr>, attrType: Constructor<PostBattleAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostBattleAbAttr>( applyAbAttrsInternal<PostBattleAbAttr>(
@ -5947,7 +5947,7 @@ export function applyPostFaintAbAttrs(
attacker?: Pokemon, attacker?: Pokemon,
move?: Move, move?: Move,
hitResult?: HitResult, hitResult?: HitResult,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostFaintAbAttr>( applyAbAttrsInternal<PostFaintAbAttr>(
@ -5963,7 +5963,7 @@ export function applyPostFaintAbAttrs(
export function applyPostItemLostAbAttrs( export function applyPostItemLostAbAttrs(
attrType: Constructor<PostItemLostAbAttr>, attrType: Constructor<PostItemLostAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostItemLostAbAttr>( applyAbAttrsInternal<PostItemLostAbAttr>(
@ -5979,14 +5979,14 @@ export function applyPostItemLostAbAttrs(
* *
* Ignores passives as they don't change and shouldn't be reapplied when main abilities change * Ignores passives as they don't change and shouldn't be reapplied when main abilities change
*/ */
export function applyOnGainAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void { export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
applySingleAbAttrs<PostSummonAbAttr>(pokemon, passive, PostSummonAbAttr, (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), args, true, simulated); applySingleAbAttrs<PostSummonAbAttr>(pokemon, passive, PostSummonAbAttr, (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), args, true, simulated);
} }
/** /**
* Clears primal weather/neutralizing gas during the turn if {@linkcode pokemon}'s ability corresponds to one * Clears primal weather/neutralizing gas during the turn if {@linkcode pokemon}'s ability corresponds to one
*/ */
export function applyOnLoseAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void { export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
applySingleAbAttrs<PreLeaveFieldAbAttr>(pokemon, passive, PreLeaveFieldAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated); applySingleAbAttrs<PreLeaveFieldAbAttr>(pokemon, passive, PreLeaveFieldAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated);
} }
function queueShowAbility(pokemon: Pokemon, passive: boolean): void { function queueShowAbility(pokemon: Pokemon, passive: boolean): void {

File diff suppressed because it is too large Load Diff

View File

@ -2022,6 +2022,7 @@ export const biomeTrainerPools: BiomeTrainerPools = {
} }
}; };
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: init methods are expected to have many lines.
export function initBiomes() { export function initBiomes() {
const pokemonBiomes = [ const pokemonBiomes = [
[ Species.BULBASAUR, PokemonType.GRASS, PokemonType.POISON, [ [ Species.BULBASAUR, PokemonType.GRASS, PokemonType.POISON, [
@ -7677,7 +7678,7 @@ export function initBiomes() {
const traverseBiome = (biome: Biome, depth: number) => { const traverseBiome = (biome: Biome, depth: number) => {
if (biome === Biome.END) { if (biome === Biome.END) {
const biomeList = Object.keys(Biome).filter(key => !isNaN(Number(key))); const biomeList = Object.keys(Biome).filter(key => !Number.isNaN(Number(key)));
biomeList.pop(); // Removes Biome.END from the list biomeList.pop(); // Removes Biome.END from the list
const randIndex = Utils.randSeedInt(biomeList.length, 1); // Will never be Biome.TOWN const randIndex = Utils.randSeedInt(biomeList.length, 1); // Will never be Biome.TOWN
biome = Biome[biomeList[randIndex]]; biome = Biome[biomeList[randIndex]];
@ -7764,7 +7765,8 @@ export function initBiomes() {
treeIndex = t; treeIndex = t;
arrayIndex = es + 1; arrayIndex = es + 1;
break; break;
} else if (speciesEvolutions && speciesEvolutions.find(se => se.speciesId === existingSpeciesId)) { }
if (speciesEvolutions?.find(se => se.speciesId === existingSpeciesId)) {
treeIndex = t; treeIndex = t;
arrayIndex = es; arrayIndex = es;
break; break;
@ -7786,7 +7788,7 @@ export function initBiomes() {
for (const b of Object.keys(biomePokemonPools)) { for (const b of Object.keys(biomePokemonPools)) {
for (const t of Object.keys(biomePokemonPools[b])) { for (const t of Object.keys(biomePokemonPools[b])) {
const tier = parseInt(t) as BiomePoolTier; const tier = Number.parseInt(t) as BiomePoolTier;
for (const tod of Object.keys(biomePokemonPools[b][t])) { for (const tod of Object.keys(biomePokemonPools[b][t])) {
const biomeTierTimePool = biomePokemonPools[b][t][tod]; const biomeTierTimePool = biomePokemonPools[b][t][tod];
for (let e = 0; e < biomeTierTimePool.length; e++) { for (let e = 0; e < biomeTierTimePool.length; e++) {
@ -7799,7 +7801,7 @@ export function initBiomes() {
}; };
for (let s = 1; s < entry.length; s++) { for (let s = 1; s < entry.length; s++) {
const speciesId = entry[s]; const speciesId = entry[s];
const prevolution = entry.map(s => pokemonEvolutions[s]).flat().find(e => e && e.speciesId === speciesId); const prevolution = entry.flatMap((s: string | number) => pokemonEvolutions[s]).find(e => e && e.speciesId === speciesId);
const level = prevolution.level - (prevolution.level === 1 ? 1 : 0) + (prevolution.wildDelay * 10) - (tier >= BiomePoolTier.BOSS ? 10 : 0); const level = prevolution.level - (prevolution.level === 1 ? 1 : 0) + (prevolution.wildDelay * 10) - (tier >= BiomePoolTier.BOSS ? 10 : 0);
if (!newEntry.hasOwnProperty(level)) { if (!newEntry.hasOwnProperty(level)) {
newEntry[level] = [ speciesId ]; newEntry[level] = [ speciesId ];

View File

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

View File

@ -92,7 +92,7 @@ export class SpeciesFormEvolution {
public item: EvolutionItem | null; public item: EvolutionItem | null;
public condition: SpeciesEvolutionCondition | null; public condition: SpeciesEvolutionCondition | null;
public wildDelay: SpeciesWildEvolutionDelay; 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) { constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) {
this.speciesId = speciesId; 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)); super(p => p.friendship >= amount && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT));
this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ]; this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ];
} else { } else {
super(p => false); super(_p => false);
this.timesOfDay = []; this.timesOfDay = [];
} }
this.amount = amount; this.amount = amount;
@ -1898,7 +1898,7 @@ export function initPokemonPrevolutions(): void {
if (ev.evoFormKey && megaFormKeys.indexOf(ev.evoFormKey) > -1) { if (ev.evoFormKey && megaFormKeys.indexOf(ev.evoFormKey) > -1) {
continue; continue;
} }
pokemonPrevolutions[ev.speciesId] = parseInt(pk) as Species; pokemonPrevolutions[ev.speciesId] = Number.parseInt(pk) as Species;
} }
}); });
} }

View File

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

View File

@ -93,7 +93,6 @@ export enum ChallengeType {
* Modifies what the pokemon stats for Flip Stat Mode. * Modifies what the pokemon stats for Flip Stat Mode.
*/ */
FLIP_STAT, FLIP_STAT,
} }
/** /**
@ -106,7 +105,7 @@ export enum MoveSourceType {
GREAT_TM, GREAT_TM,
ULTRA_TM, ULTRA_TM,
COMMON_EGG, COMMON_EGG,
RARE_EGG RARE_EGG,
} }
/** /**
@ -148,7 +147,10 @@ export abstract class Challenge {
* @returns {@link string} The i18n key for this challenge * @returns {@link string} The i18n key for this challenge
*/ */
geti18nKey(): string { 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 +276,93 @@ export abstract class Challenge {
* @param source The source challenge or json. * @param source The source challenge or json.
* @returns This challenge. * @returns This challenge.
*/ */
static loadChallenge(source: Challenge | any): Challenge { static loadChallenge(_source: Challenge | any): Challenge {
throw new Error("Method not implemented! Use derived class"); throw new Error("Method not implemented! Use derived class");
} }
/** /**
* An apply function for STARTER_CHOICE challenges. Derived classes should alter this. * An apply function for STARTER_CHOICE challenges. Derived classes should alter this.
* @param pokemon {@link PokemonSpecies} The pokemon to check the validity of. * @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 _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 _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 _soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @returns {@link boolean} Whether this function did anything. * @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,
_soft = false,
): boolean {
return false; return false;
} }
/** /**
* An apply function for STARTER_POINTS challenges. Derived classes should alter this. * 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. * @returns {@link boolean} Whether this function did anything.
*/ */
applyStarterPoints(points: Utils.NumberHolder): boolean { applyStarterPoints(_points: Utils.NumberHolder): boolean {
return false; return false;
} }
/** /**
* An apply function for STARTER_COST challenges. Derived classes should alter this. * An apply function for STARTER_COST challenges. Derived classes should alter this.
* @param species {@link Species} The pokemon to change the cost of. * @param _species {@link Species} The pokemon to change the cost of.
* @param cost {@link Utils.NumberHolder} The cost of the starter. * @param _cost {@link Utils.NumberHolder} The cost of the starter.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean { applyStarterCost(_species: Species, _cost: Utils.NumberHolder): boolean {
return false; return false;
} }
/** /**
* An apply function for STARTER_MODIFY challenges. Derived classes should alter this. * 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. * @returns {@link boolean} Whether this function did anything.
*/ */
applyStarterModify(pokemon: Pokemon): boolean { applyStarterModify(_pokemon: Pokemon): boolean {
return false; return false;
} }
/** /**
* An apply function for POKEMON_IN_BATTLE challenges. Derived classes should alter this. * 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 _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 _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. * @returns {@link boolean} Whether this function did anything.
*/ */
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { applyPokemonInBattle(_pokemon: Pokemon, _valid: Utils.BooleanHolder): boolean {
return false; return false;
} }
/** /**
* An apply function for FIXED_BATTLE challenges. Derived classes should alter this. * An apply function for FIXED_BATTLE challenges. Derived classes should alter this.
* @param waveIndex {@link Number} The current wave index. * @param _waveIndex {@link Number} The current wave index.
* @param battleConfig {@link FixedBattleConfig} The battle config to modify. * @param _battleConfig {@link FixedBattleConfig} The battle config to modify.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean { applyFixedBattle(_waveIndex: number, _battleConfig: FixedBattleConfig): boolean {
return false; return false;
} }
/** /**
* An apply function for TYPE_EFFECTIVENESS challenges. Derived classes should alter this. * 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. * @returns Whether this function did anything.
*/ */
applyTypeEffectiveness(effectiveness: Utils.NumberHolder): boolean { applyTypeEffectiveness(_effectiveness: Utils.NumberHolder): boolean {
return false; return false;
} }
/** /**
* An apply function for AI_LEVEL challenges. Derived classes should alter this. * An apply function for AI_LEVEL challenges. Derived classes should alter this.
* @param level {@link Utils.NumberHolder} The generated level. * @param _level {@link Utils.NumberHolder} The generated level.
* @param levelCap {@link Number} The current level cap. * @param _levelCap {@link Number} The current level cap.
* @param isTrainer {@link Boolean} Whether this is a trainer pokemon. * @param _isTrainer {@link Boolean} Whether this is a trainer pokemon.
* @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon. * @param _isBoss {@link Boolean} Whether this is a non-trainer boss pokemon.
* @returns {@link boolean} Whether this function did anything. * @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; return false;
} }
@ -365,7 +372,7 @@ export abstract class Challenge {
* @param moveSlots {@link Utils.NumberHolder} The amount of move slots. * @param moveSlots {@link Utils.NumberHolder} The amount of move slots.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyMoveSlot(pokemon: Pokemon, moveSlots: Utils.NumberHolder): boolean { applyMoveSlot(_pokemon: Pokemon, _moveSlots: Utils.NumberHolder): boolean {
return false; return false;
} }
@ -375,7 +382,7 @@ export abstract class Challenge {
* @param hasPassive {@link Utils.BooleanHolder} Whether it should have its passive. * @param hasPassive {@link Utils.BooleanHolder} Whether it should have its passive.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyPassiveAccess(pokemon: Pokemon, hasPassive: Utils.BooleanHolder): boolean { applyPassiveAccess(_pokemon: Pokemon, _hasPassive: Utils.BooleanHolder): boolean {
return false; return false;
} }
@ -384,41 +391,46 @@ export abstract class Challenge {
* @param gameMode {@link GameMode} The current game mode. * @param gameMode {@link GameMode} The current game mode.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyGameModeModify(gameMode: GameMode): boolean { applyGameModeModify(_gameMode: GameMode): boolean {
return false; return false;
} }
/** /**
* An apply function for MOVE_ACCESS. Derived classes should alter this. * An apply function for MOVE_ACCESS. Derived classes should alter this.
* @param pokemon {@link Pokemon} What pokemon would learn 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 _moveSource {@link MoveSourceType} What source the pokemon would get the move from.
* @param move {@link Moves} The move in question. * @param _move {@link Moves} The move in question.
* @param level {@link Utils.NumberHolder} The level threshold for access. * @param _level {@link Utils.NumberHolder} The level threshold for access.
* @returns {@link boolean} Whether this function did anything. * @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; return false;
} }
/** /**
* An apply function for MOVE_WEIGHT. Derived classes should alter this. * An apply function for MOVE_WEIGHT. Derived classes should alter this.
* @param pokemon {@link Pokemon} What pokemon would learn 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 _moveSource {@link MoveSourceType} What source the pokemon would get the move from.
* @param move {@link Moves} The move in question. * @param _move {@link Moves} The move in question.
* @param weight {@link Utils.NumberHolder} The base weight of the move * @param _weight {@link Utils.NumberHolder} The base weight of the move
* @returns {@link boolean} Whether this function did anything. * @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; return false;
} }
/** /**
* An apply function for FlipStats. Derived classes should alter this. * An apply function for FlipStats. Derived classes should alter this.
* @param pokemon {@link Pokemon} What pokemon would learn the move. * @param _pokemon {@link Pokemon} What pokemon would learn the move.
* @param baseStats What are the stats to flip. * @param _baseStats What are the stats to flip.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyFlipStat(pokemon: Pokemon, baseStats: number[]) { applyFlipStat(_pokemon: Pokemon, _baseStats: number[]) {
return false; return false;
} }
} }
@ -433,7 +445,12 @@ export class SingleGenerationChallenge extends Challenge {
super(Challenges.SINGLE_GENERATION, 9); super(Challenges.SINGLE_GENERATION, 9);
} }
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean { applyStarterChoice(
pokemon: PokemonSpecies,
valid: Utils.BooleanHolder,
_dexAttr: DexAttrProps,
soft = false,
): boolean {
const generations = [pokemon.generation]; const generations = [pokemon.generation];
if (soft) { if (soft) {
const speciesToCheck = [pokemon.speciesId]; const speciesToCheck = [pokemon.speciesId];
@ -458,7 +475,10 @@ export class SingleGenerationChallenge extends Challenge {
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
const baseGeneration = getPokemonSpecies(pokemon.species.speciesId).generation; const baseGeneration = getPokemonSpecies(pokemon.species.speciesId).generation;
const fusionGeneration = pokemon.isFusion() ? getPokemonSpecies(pokemon.fusionSpecies!.speciesId).generation : 0; // TODO: is the bang on fusionSpecies correct? 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; valid.value = false;
return true; return true;
} }
@ -467,11 +487,63 @@ export class SingleGenerationChallenge extends Challenge {
applyFixedBattle(waveIndex: number, battleConfig: FixedBattleConfig): boolean { applyFixedBattle(waveIndex: number, battleConfig: FixedBattleConfig): boolean {
let trainerTypes: (TrainerType | TrainerType[])[] = []; 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 evilTeamWaves: number[] = [
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 ]]; ClassicFixedBossWaves.EVIL_GRUNT_1,
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 ]]; ClassicFixedBossWaves.EVIL_GRUNT_2,
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 ]]; ClassicFixedBossWaves.EVIL_GRUNT_3,
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 ]]; 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) { switch (waveIndex) {
case ClassicFixedBossWaves.EVIL_GRUNT_1: case ClassicFixedBossWaves.EVIL_GRUNT_1:
trainerTypes = evilTeamGrunts[this.value - 1]; trainerTypes = evilTeamGrunts[this.value - 1];
@ -488,42 +560,123 @@ export class SingleGenerationChallenge extends Challenge {
break; break;
case ClassicFixedBossWaves.EVIL_BOSS_1: case ClassicFixedBossWaves.EVIL_BOSS_1:
trainerTypes = evilTeamBosses[this.value - 1]; trainerTypes = evilTeamBosses[this.value - 1];
battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) battleConfig
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }); .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; return true;
case ClassicFixedBossWaves.EVIL_BOSS_2: case ClassicFixedBossWaves.EVIL_BOSS_2:
trainerTypes = evilTeamBossRematches[this.value - 1]; trainerTypes = evilTeamBossRematches[this.value - 1];
battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) battleConfig
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }); .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; return true;
case ClassicFixedBossWaves.ELITE_FOUR_1: 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; break;
case ClassicFixedBossWaves.ELITE_FOUR_2: 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; break;
case ClassicFixedBossWaves.ELITE_FOUR_3: 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; break;
case ClassicFixedBossWaves.ELITE_FOUR_4: 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; break;
case ClassicFixedBossWaves.CHAMPION: 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; break;
} }
if (trainerTypes.length === 0) { if (trainerTypes.length === 0) {
return false; 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 +709,11 @@ export class SingleGenerationChallenge extends Challenge {
if (value === 0) { if (value === 0) {
return i18next.t("challenges:singleGeneration.desc_default"); 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 { static loadChallenge(source: SingleGenerationChallenge | any): SingleGenerationChallenge {
const newChallenge = new SingleGenerationChallenge(); const newChallenge = new SingleGenerationChallenge();
newChallenge.value = source.value; newChallenge.value = source.value;
@ -591,7 +745,12 @@ export class SingleTypeChallenge extends Challenge {
super(Challenges.SINGLE_TYPE, 18); 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,
soft = false,
): boolean {
const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex); const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex);
const types = [speciesForm.type1, speciesForm.type2]; const types = [speciesForm.type1, speciesForm.type2];
if (soft && !SingleTypeChallenge.SPECIES_OVERRIDES.includes(pokemon.speciesId)) { if (soft && !SingleTypeChallenge.SPECIES_OVERRIDES.includes(pokemon.speciesId)) {
@ -623,8 +782,16 @@ export class SingleTypeChallenge extends Challenge {
} }
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
if (pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true) if (
&& !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? 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; valid.value = false;
return true; return true;
} }
@ -662,7 +829,9 @@ export class SingleTypeChallenge extends Challenge {
const type = i18next.t(`pokemonInfo:Type.${PokemonType[this.value - 1]}`); 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 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 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; return this.value === 0 ? defaultDesc : typeDesc;
} }
@ -702,7 +871,12 @@ export class FreshStartChallenge extends Challenge {
pokemon.abilityIndex = 0; // Always base ability, not hidden ability pokemon.abilityIndex = 0; // Always base ability, not hidden ability
pokemon.passive = false; // Passive isn't unlocked pokemon.passive = false; // Passive isn't unlocked
pokemon.nature = Nature.HARDY; // Neutral nature 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.luck = 0; // No luck
pokemon.shiny = false; // Not shiny pokemon.shiny = false; // Not shiny
pokemon.variant = 0; // Not shiny pokemon.variant = 0; // Not shiny
@ -747,7 +921,8 @@ export class InverseBattleChallenge extends Challenge {
if (effectiveness.value < 1) { if (effectiveness.value < 1) {
effectiveness.value = 2; effectiveness.value = 2;
return true; return true;
} else if (effectiveness.value > 1) { }
if (effectiveness.value > 1) {
effectiveness.value = 0.5; effectiveness.value = 0.5;
return true; return true;
} }
@ -764,7 +939,7 @@ export class FlipStatChallenge extends Challenge {
super(Challenges.FLIP_STAT, 1); super(Challenges.FLIP_STAT, 1);
} }
override applyFlipStat(pokemon: Pokemon, baseStats: number[]) { override applyFlipStat(_pokemon: Pokemon, baseStats: number[]) {
const origStats = Utils.deepCopy(baseStats); const origStats = Utils.deepCopy(baseStats);
baseStats[0] = origStats[5]; baseStats[0] = origStats[5];
baseStats[1] = origStats[4]; baseStats[1] = origStats[4];
@ -858,7 +1033,14 @@ export class LowerStarterPointsChallenge extends Challenge {
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon. * @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @returns True if any challenge was successfully applied. * @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,
soft: boolean,
): boolean;
/** /**
* Apply all challenges that modify available total starter points. * Apply all challenges that modify available total starter points.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -866,7 +1048,11 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @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 True if any challenge was successfully applied. * @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. * Apply all challenges that modify the cost of a starter.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -875,7 +1061,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param points {@link Utils.NumberHolder} The cost of the pokemon. * @param points {@link Utils.NumberHolder} The cost of the pokemon.
* @returns True if any challenge was successfully applied. * @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. * Apply all challenges that modify a starter after selection.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -883,7 +1074,11 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param pokemon {@link Pokemon} The starter pokemon to modify. * @param pokemon {@link Pokemon} The starter pokemon to modify.
* @returns True if any challenge was successfully applied. * @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. * Apply all challenges that what pokemon you can have in battle.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -892,7 +1087,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. * @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. * @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. * Apply all challenges that modify what fixed battles there are.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -901,7 +1101,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param battleConfig {@link FixedBattleConfig} The battle config to modify. * @param battleConfig {@link FixedBattleConfig} The battle config to modify.
* @returns True if any challenge was successfully applied. * @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. * Apply all challenges that modify type effectiveness.
* @param gameMode {@linkcode GameMode} The current gameMode * @param gameMode {@linkcode GameMode} The current gameMode
@ -909,7 +1114,11 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move. * @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move.
* @returns True if any challenge was successfully applied. * @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. * Apply all challenges that modify what level AI are.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -920,7 +1129,14 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon. * @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon.
* @returns True if any challenge was successfully applied. * @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. * Apply all challenges that modify how many move slots the AI has.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -929,7 +1145,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param moveSlots {@link Utils.NumberHolder} The amount of move slots. * @param moveSlots {@link Utils.NumberHolder} The amount of move slots.
* @returns True if any challenge was successfully applied. * @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. * Apply all challenges that modify whether a pokemon has its passive.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -938,7 +1159,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param hasPassive {@link Utils.BooleanHolder} Whether it has its passive. * @param hasPassive {@link Utils.BooleanHolder} Whether it has its passive.
* @returns True if any challenge was successfully applied. * @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. * Apply all challenges that modify the game modes settings.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -956,7 +1182,14 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param level {@link Utils.NumberHolder} The level threshold for access. * @param level {@link Utils.NumberHolder} The level threshold for access.
* @returns True if any challenge was successfully applied. * @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 * Apply all challenges that modify what weight a pokemon gives to move generation
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -967,9 +1200,21 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param weight {@link Utils.NumberHolder} The weight of the move. * @param weight {@link Utils.NumberHolder} The weight of the move.
* @returns True if any challenge was successfully applied. * @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 { export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean {
let ret = false; let ret = false;
@ -1057,6 +1302,6 @@ export function initChallenges() {
new SingleTypeChallenge(), new SingleTypeChallenge(),
new FreshStartChallenge(), new FreshStartChallenge(),
new InverseBattleChallenge(), new InverseBattleChallenge(),
new FlipStatChallenge() new FlipStatChallenge(),
); );
} }

View File

@ -16,7 +16,7 @@ export interface DailyRunConfig {
} }
export function fetchDailyRunSeed(): Promise<string | null> { 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 => { pokerogueApi.daily.getSeed().then(dailySeed => {
resolve(dailySeed); resolve(dailySeed);
}); });
@ -26,13 +26,17 @@ export function fetchDailyRunSeed(): Promise<string | null> {
export function getDailyRunStarters(seed: string): Starter[] { export function getDailyRunStarters(seed: string): Starter[] {
const starters: Starter[] = []; const starters: Starter[] = [];
globalScene.executeWithSeedOffset(() => { globalScene.executeWithSeedOffset(
() => {
const startingLevel = globalScene.gameMode.getStartingLevel(); const startingLevel = globalScene.gameMode.getStartingLevel();
if (/\d{18}$/.test(seed)) { if (/\d{18}$/.test(seed)) {
for (let s = 0; s < 3; s++) { for (let s = 0; s < 3; s++) {
const offset = 6 + s * 6; 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)); starters.push(getDailyRunStarter(starterSpeciesForm, startingLevel));
} }
return; return;
@ -46,35 +50,52 @@ export function getDailyRunStarters(seed: string): Starter[] {
for (let c = 0; c < starterCosts.length; c++) { for (let c = 0; c < starterCosts.length; c++) {
const cost = starterCosts[c]; const cost = starterCosts[c];
const costSpecies = Object.keys(speciesStarterCosts) const costSpecies = Object.keys(speciesStarterCosts)
.map(s => parseInt(s) as Species) .map(s => Number.parseInt(s) as Species)
.filter(s => speciesStarterCosts[s] === cost); .filter(s => speciesStarterCosts[s] === cost);
const randPkmSpecies = getPokemonSpecies(Utils.randSeedItem(costSpecies)); 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)); starters.push(getDailyRunStarter(starterSpecies, startingLevel));
} }
}, 0, seed); },
0,
seed,
);
return starters; return starters;
} }
function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLevel: number): Starter { 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 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 = { const starter: Starter = {
species: starterSpecies, species: starterSpecies,
dexAttr: pokemon.getDexAttr(), dexAttr: pokemon.getDexAttr(),
abilityIndex: pokemon.abilityIndex, abilityIndex: pokemon.abilityIndex,
passive: false, passive: false,
nature: pokemon.getNature(), nature: pokemon.getNature(),
pokerus: pokemon.pokerus pokerus: pokemon.pokerus,
}; };
pokemon.destroy(); pokemon.destroy();
return starter; return starter;
} }
interface BiomeWeights { interface BiomeWeights {
[key: number]: number [key: number]: number;
} }
// Initially weighted by amount of exits each biome has // 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, seenCount: currDexEntry.seenCount,
caughtCount: currDexEntry.caughtCount, caughtCount: currDexEntry.caughtCount,
hatchedCount: currDexEntry.hatchedCount, hatchedCount: currDexEntry.hatchedCount,
ivs: [ ...currDexEntry.ivs ] ivs: [...currDexEntry.ivs],
}; };
this.starterDataEntryBeforeUpdate = { this.starterDataEntryBeforeUpdate = {
moveset: currStarterDataEntry.moveset, moveset: currStarterDataEntry.moveset,
@ -55,7 +55,7 @@ export class EggHatchData {
abilityAttr: currStarterDataEntry.abilityAttr, abilityAttr: currStarterDataEntry.abilityAttr,
passiveAttr: currStarterDataEntry.passiveAttr, passiveAttr: currStarterDataEntry.passiveAttr,
valueReduction: currStarterDataEntry.valueReduction, 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) * @param showMessage boolean to show messages for the new catches and egg moves (false by default)
* @returns * @returns
*/ */
updatePokemon(showMessage : boolean = false) { updatePokemon(showMessage = false) {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
globalScene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => { globalScene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => {
globalScene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs); 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); this.setEggMoveUnlocked(value);
resolve(); resolve();
}); });

View File

@ -12,7 +12,31 @@ import i18next from "i18next";
import { EggTier } from "#enums/egg-type"; import { EggTier } from "#enums/egg-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { EggSourceType } from "#enums/egg-source-types"; 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"; import { speciesEggTiers } from "#app/data/balance/species-egg-tiers";
export const EGG_SEED = 1073741824; export const EGG_SEED = 1073741824;
@ -60,7 +84,6 @@ export interface IEggOptions {
} }
export class Egg { export class Egg {
//// ////
// #region Private properties // #region Private properties
//// ////
@ -141,7 +164,7 @@ export class Egg {
this._sourceType = eggOptions?.sourceType!; // TODO: is this bang correct? this._sourceType = eggOptions?.sourceType!; // TODO: is this bang correct?
// Ensure _sourceType is defined before invoking rollEggTier(), as it is referenced // 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 egg was pulled, check if egg pity needs to override the egg tier
if (eggOptions?.pulled) { if (eggOptions?.pulled) {
// Needs this._tier and this._sourceType to work // 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 // First roll shiny and variant so we can filter if species with an variant exist
this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny()); this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
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._species = eggOptions?.species ?? this.rollSpecies()!; // TODO: Is this bang correct?
this._overrideHiddenAbility = eggOptions?.overrideHiddenAbility ?? false; this._overrideHiddenAbility = eggOptions?.overrideHiddenAbility ?? false;
@ -181,9 +204,13 @@ export class Egg {
}; };
const seedOverride = Utils.randomString(24); const seedOverride = Utils.randomString(24);
globalScene.executeWithSeedOffset(() => { globalScene.executeWithSeedOffset(
() => {
generateEggProperties(eggOptions); generateEggProperties(eggOptions);
}, 0, seedOverride); },
0,
seedOverride,
);
this._eggDescriptor = eggOptions?.eggDescriptor; this._eggDescriptor = eggOptions?.eggDescriptor;
} }
@ -193,8 +220,11 @@ export class Egg {
//// ////
public isManaphyEgg(): boolean { public isManaphyEgg(): boolean {
return (this._species === Species.PHIONE || this._species === Species.MANAPHY) || return (
this._tier === EggTier.COMMON && !(this._id % 204) && !this._species; this._species === Species.PHIONE ||
this._species === Species.MANAPHY ||
(this._tier === EggTier.COMMON && !(this._id % 204) && !this._species)
);
} }
public getKey(): string { public getKey(): string {
@ -218,14 +248,18 @@ export class Egg {
let pokemonSpecies = getPokemonSpecies(this._species); let pokemonSpecies = getPokemonSpecies(this._species);
// Special condition to have Phione eggs also have a chance of generating Manaphy // Special condition to have Phione eggs also have a chance of generating Manaphy
if (this._species === Species.PHIONE && this._sourceType === EggSourceType.SAME_SPECIES_EGG) { 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 // Sets the hidden ability if a hidden ability exists and
// the override is set or the egg hits the chance // the override is set or the egg hits the chance
let abilityIndex: number | undefined = undefined; let abilityIndex: number | undefined = undefined;
const sameSpeciesEggHACheck = (this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE)); const sameSpeciesEggHACheck =
const gachaEggHACheck = (!(this._sourceType === EggSourceType.SAME_SPECIES_EGG) && !Utils.randSeedInt(GACHA_EGG_HA_RATE)); 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)) { if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility || sameSpeciesEggHACheck || gachaEggHACheck)) {
abilityIndex = 2; abilityIndex = 2;
} }
@ -243,9 +277,13 @@ export class Egg {
}; };
ret = ret!; // Tell TS compiler it's defined now ret = ret!; // Tell TS compiler it's defined now
globalScene.executeWithSeedOffset(() => { globalScene.executeWithSeedOffset(
() => {
generatePlayerPokemonHelper(); generatePlayerPokemonHelper();
}, this._id, EGG_SEED.toString()); },
this._id,
EGG_SEED.toString(),
);
return ret; return ret;
} }
@ -287,9 +325,17 @@ export class Egg {
public getEggTypeDescriptor(): string { public getEggTypeDescriptor(): string {
switch (this.sourceType) { switch (this.sourceType) {
case EggSourceType.SAME_SPECIES_EGG: 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: 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: case EggSourceType.GACHA_SHINY:
return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny"); return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny");
case EggSourceType.GACHA_MOVE: case EggSourceType.GACHA_MOVE:
@ -344,9 +390,16 @@ export class Egg {
} }
private rollEggTier(): EggTier { 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); 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 { private rollSpecies(): Species | null {
@ -364,10 +417,10 @@ export class Egg {
* when Utils.randSeedInt(8) = 1, and by making the generatePlayerPokemon() species * 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. * 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; 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)) { if (!Utils.randSeedInt(2)) {
return getLegendaryGachaSpeciesForTimestamp(this.timestamp); return getLegendaryGachaSpeciesForTimestamp(this.timestamp);
} }
@ -399,13 +452,21 @@ export class Egg {
let speciesPool = Object.keys(speciesEggTiers) let speciesPool = Object.keys(speciesEggTiers)
.filter(s => speciesEggTiers[s] === this.tier) .filter(s => speciesEggTiers[s] === this.tier)
.map(s => parseInt(s) as Species) .map(s => Number.parseInt(s) as Species)
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1); .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 this is the 10th egg without unlocking something new, attempt to force it.
if (globalScene.gameData.unlockPity[this.tier] >= 9) { 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)); const lockedPool = speciesPool.filter(
if (lockedPool.length) { // Skip this if everything is unlocked 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; speciesPool = lockedPool;
} }
} }
@ -431,7 +492,9 @@ export class Egg {
for (const speciesId of speciesPool) { for (const speciesId of speciesPool) {
// Accounts for species that have starter costs outside of the normal range for their EggTier // 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 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); speciesWeights.push(totalWeight + weight);
totalWeight += weight; totalWeight += weight;
} }
@ -447,7 +510,10 @@ export class Egg {
} }
species = species!; // tell TS compiled it's defined now! 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); globalScene.gameData.unlockPity[this.tier] = Math.min(globalScene.gameData.unlockPity[this.tier] + 1, 10);
} else { } else {
globalScene.gameData.unlockPity[this.tier] = 0; globalScene.gameData.unlockPity[this.tier] = 0;
@ -487,20 +553,24 @@ export class Egg {
const rand = Utils.randSeedInt(10); const rand = Utils.randSeedInt(10);
if (rand >= SHINY_VARIANT_CHANCE) { if (rand >= SHINY_VARIANT_CHANCE) {
return VariantTier.STANDARD; // 6/10 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 { 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.RARE] += 1;
globalScene.gameData.eggPity[EggTier.EPIC] += 1; globalScene.gameData.eggPity[EggTier.EPIC] += 1;
globalScene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset; 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. // 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; this._tier = EggTier.LEGENDARY;
} else if (globalScene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) { } else if (globalScene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.EPIC; this._tier = EggTier.EPIC;
@ -542,7 +612,7 @@ export class Egg {
export function getValidLegendaryGachaSpecies(): Species[] { export function getValidLegendaryGachaSpecies(): Species[] {
return Object.entries(speciesEggTiers) return Object.entries(speciesEggTiers)
.filter(s => s[1] === EggTier.LEGENDARY) .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); .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 offset = Math.floor(Math.floor(dayTimestamp / 86400000) / legendarySpecies.length); // Cycle number
const index = Math.floor(dayTimestamp / 86400000) % legendarySpecies.length; // Index within cycle const index = Math.floor(dayTimestamp / 86400000) % legendarySpecies.length; // Index within cycle
globalScene.executeWithSeedOffset(() => { globalScene.executeWithSeedOffset(
() => {
ret = Phaser.Math.RND.shuffle(legendarySpecies)[index]; ret = Phaser.Math.RND.shuffle(legendarySpecies)[index];
}, offset, EGG_SEED.toString()); },
offset,
EGG_SEED.toString(),
);
ret = ret!; // tell TS compiler it's ret = ret!; // tell TS compiler it's
return ret; return ret;

View File

@ -4,16 +4,64 @@ export enum GrowthRate {
MEDIUM_FAST, MEDIUM_FAST,
MEDIUM_SLOW, MEDIUM_SLOW,
SLOW, SLOW,
FLUCTUATING FLUCTUATING,
} }
const expLevels = [ 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, 15, 52, 122, 237, 406, 637, 942, 1326, 1800, 2369, 3041, 3822, 4719, 5737, 6881, 8155, 9564, 11111, 12800, 14632,
[ 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 ], 16610, 18737, 21012, 23437, 26012, 28737, 31610, 34632, 37800, 41111, 44564, 48155, 51881, 55737, 59719, 63822,
[ 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 ], 68041, 72369, 76800, 81326, 85942, 90637, 95406, 100237, 105122, 110052, 115015, 120001, 125000, 131324, 137795,
[ 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 ], 144410, 151165, 158056, 165079, 172229, 179503, 186894, 194400, 202013, 209728, 217540, 225443, 233431, 241496,
[ 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 ] 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 { export function getLevelTotalExp(level: number, growthRate: GrowthRate): number {
@ -29,22 +77,22 @@ export function getLevelTotalExp(level: number, growthRate: GrowthRate): number
switch (growthRate) { switch (growthRate) {
case GrowthRate.ERRATIC: 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; break;
case GrowthRate.FAST: case GrowthRate.FAST:
ret = Math.pow(level, 3) * 4 / 5; ret = (Math.pow(level, 3) * 4) / 5;
break; break;
case GrowthRate.MEDIUM_FAST: case GrowthRate.MEDIUM_FAST:
ret = Math.pow(level, 3); ret = Math.pow(level, 3);
break; break;
case GrowthRate.MEDIUM_SLOW: 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; break;
case GrowthRate.SLOW: case GrowthRate.SLOW:
ret = Math.pow(level, 3) * 5 / 4; ret = (Math.pow(level, 3) * 5) / 4;
break; break;
case GrowthRate.FLUCTUATING: 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; break;
} }

View File

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

View File

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

View File

@ -1,5 +1,11 @@
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, 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 type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; 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 { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils"; import { randInt } from "#app/utils";
import { BattlerIndex } from "#app/battle"; 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 { TrainerSlot } from "#app/data/trainer-config";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3805 | GitHub Issue #3805}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const AbsoluteAvariceEncounter: MysteryEncounter = export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.ABSOLUTE_AVARICE) MysteryEncounterType.ABSOLUTE_AVARICE,
)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Must have at least 4 berries to spawn .withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Must have at least 4 berries to spawn
@ -53,7 +64,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
hasShadow: true, hasShadow: true,
alpha: 0.001, alpha: 0.001,
repeat: true, repeat: true,
x: -5 x: -5,
}, },
{ {
spriteKey: "", spriteKey: "",
@ -61,7 +72,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
species: Species.GREEDENT, species: Species.GREEDENT,
hasShadow: false, hasShadow: false,
repeat: true, repeat: true,
x: -5 x: -5,
}, },
{ {
spriteKey: "lum_berry", spriteKey: "lum_berry",
@ -70,7 +81,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 7, x: 7,
y: -14, y: -14,
hidden: true, hidden: true,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: "salac_berry", spriteKey: "salac_berry",
@ -79,7 +90,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 2, x: 2,
y: 4, y: 4,
hidden: true, hidden: true,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: "lansat_berry", spriteKey: "lansat_berry",
@ -88,7 +99,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 32, x: 32,
y: 5, y: 5,
hidden: true, hidden: true,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: "liechi_berry", spriteKey: "liechi_berry",
@ -97,7 +108,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 6, x: 6,
y: -5, y: -5,
hidden: true, hidden: true,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: "sitrus_berry", spriteKey: "sitrus_berry",
@ -106,7 +117,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 7, x: 7,
y: 8, y: 8,
hidden: true, hidden: true,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: "enigma_berry", spriteKey: "enigma_berry",
@ -115,7 +126,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 26, x: 26,
y: -4, y: -4,
hidden: true, hidden: true,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: "leppa_berry", spriteKey: "leppa_berry",
@ -124,7 +135,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 16, x: 16,
y: -27, y: -27,
hidden: true, hidden: true,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: "petaya_berry", spriteKey: "petaya_berry",
@ -133,7 +144,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 30, x: 30,
y: -17, y: -17,
hidden: true, hidden: true,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: "ganlon_berry", spriteKey: "ganlon_berry",
@ -142,7 +153,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 16, x: 16,
y: -11, y: -11,
hidden: true, hidden: true,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: "apicot_berry", spriteKey: "apicot_berry",
@ -151,7 +162,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 14, x: 14,
y: -2, y: -2,
hidden: true, hidden: true,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: "starf_berry", spriteKey: "starf_berry",
@ -160,7 +171,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: 18, x: 18,
y: 9, y: 9,
hidden: true, hidden: true,
disableAnimation: true disableAnimation: true,
}, },
]) ])
.withHideWildIntroMessage(true) .withHideWildIntroMessage(true)
@ -168,7 +179,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}:intro`, text: `${namespace}:intro`,
} },
]) ])
.setLocalizationKey(`${namespace}`) .setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .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 // 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 // Overflow berries will be "lost" on the boss, but it's un-catchable anyway
for (let i = 0; i < berryMod.stackCount; i++) { 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 }); 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 // 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 // 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 ? const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
[ Stat.SPDEF ] : globalScene.currentBattle.waveIndex < 50 ? [Stat.SPDEF] : [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
// Calculate boss mon // Calculate boss mon
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
@ -226,9 +238,11 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.boss_enraged`); 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; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, 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 // Provides 1x Reviver Seed to each party member at end of battle
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED); 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 givePartyPokemonReviverSeeds = () => {
const party = globalScene.getPlayerParty(); const party = globalScene.getPlayerParty();
party.forEach(p => { party.forEach(p => {
@ -288,17 +304,16 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY], targets: [BattlerIndex.ENEMY],
move: new PokemonMove(Moves.STUFF_CHEEKS), move: new PokemonMove(Moves.STUFF_CHEEKS),
ignorePp: true ignorePp: true,
}); });
await transitionMysteryEncounterIntroVisuals(true, true, 500); await transitionMysteryEncounterIntroVisuals(true, true, 500);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
@ -318,7 +333,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id); const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
const berryTypesAsArray: BerryType[] = []; const berryTypesAsArray: BerryType[] = [];
stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.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) { if (returnedBerryCount > 0) {
for (let i = 0; i < returnedBerryCount; i++) { for (let i = 0; i < returnedBerryCount; i++) {
@ -336,11 +351,10 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
await transitionMysteryEncounterIntroVisuals(true, true, 500); await transitionMysteryEncounterIntroVisuals(true, true, 500);
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, 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) // Greedent joins the team, level equal to 2 below highest party member (shiny locked)
const level = getHighestLevelPlayerPokemon(false, true).level - 2; const level = getHighestLevelPlayerPokemon(false, true).level - 2;
const greedent = new EnemyPokemon(getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true); 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; greedent.passive = true;
await transitionMysteryEncounterIntroVisuals(true, true, 500); await transitionMysteryEncounterIntroVisuals(true, true, 500);
await catchPokemon(greedent, null, PokeballType.POKEBALL, false); await catchPokemon(greedent, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build() .build(),
) )
.build(); .build();
@ -382,70 +401,79 @@ function doGreedentSpriteSteal() {
globalScene.tweens.chain({ globalScene.tweens.chain({
targets: greedentSprites, targets: greedentSprites,
tweens: [ tweens: [
{ // Slide Greedent diagonally {
// Slide Greedent diagonally
duration: slideDelay, duration: slideDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
y: "+=75", y: "+=75",
x: "-=65", x: "-=65",
scale: 1.1 scale: 1.1,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Slide Greedent diagonally {
// Slide Greedent diagonally
duration: slideDelay, duration: slideDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
y: "-=75", y: "-=75",
x: "+=65", x: "+=65",
scale: 1 scale: 1,
}, },
{ // Bounce at the end {
// Bounce at the end
duration: 300, duration: 300,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
y: "-=20", y: "-=20",
loop: 1, loop: 1,
} },
] ],
}); });
} }
@ -467,16 +495,28 @@ function doGreedentEatBerries() {
globalScene.playSound("battle_anims/PRSFX- Bug Bite"); globalScene.playSound("battle_anims/PRSFX- Bug Bite");
} }
index++; index++;
} },
}); });
} }
/** /**
* @param isEat Default false. Will "create" pile when false, and remove pile when true. * @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; 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) { if (isEat) {
animationOrder = animationOrder.reverse(); animationOrder = animationOrder.reverse();
} }
@ -515,7 +555,7 @@ function doBerryBounce(berrySprites: Phaser.GameObjects.Sprite[], yd: number, ba
globalScene.tweens.add({ globalScene.tweens.add({
targets: berrySprites, targets: berrySprites,
y: "+=" + bounceYOffset, y: "+=" + bounceYOffset,
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" }, x: { value: "+=" + bouncePower * bouncePower * 10, ease: "Linear" },
duration: bouncePower * baseBounceDuration, duration: bouncePower * baseBounceDuration,
ease: "Cubic.easeIn", ease: "Cubic.easeIn",
onComplete: () => { onComplete: () => {
@ -527,13 +567,13 @@ function doBerryBounce(berrySprites: Phaser.GameObjects.Sprite[], yd: number, ba
globalScene.tweens.add({ globalScene.tweens.add({
targets: berrySprites, targets: berrySprites,
y: "-=" + bounceYOffset, y: "-=" + bounceYOffset,
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" }, x: { value: "+=" + bouncePower * bouncePower * 10, ease: "Linear" },
duration: bouncePower * baseBounceDuration, duration: bouncePower * baseBounceDuration,
ease: "Cubic.easeOut", 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 { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; 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 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 { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; 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 { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const AnOfferYouCantRefuseEncounter: MysteryEncounter = export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE) MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE,
)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
@ -46,7 +56,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
repeat: true, repeat: true,
x: 0, x: 0,
y: -4, y: -4,
yShadow: -4 yShadow: -4,
}, },
{ {
spriteKey: "rich_kid_m", spriteKey: "rich_kid_m",
@ -54,7 +64,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
hasShadow: true, hasShadow: true,
x: 2, x: 2,
y: 5, y: 5,
yShadow: 5 yShadow: 5,
}, },
]) ])
.withIntroDialogue([ .withIntroDialogue([
@ -76,7 +86,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId(); const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId();
const starterValue: number = speciesStarterCosts[baseSpecies] ?? 1; 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); const price = globalScene.getWaveMoneyAmount(multiplier);
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender()); encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
@ -85,7 +95,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
// Store pokemon and price // Store pokemon and price
encounter.misc = { encounter.misc = {
pokemon: pokemon, pokemon: pokemon,
price: price price: price,
}; };
// If player meets the combo OR requirements for option 2, populate the token // If player meets the combo OR requirements for option 2, populate the token
@ -107,8 +117,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
return true; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
@ -131,16 +140,15 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM)); globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM));
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement( .withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some( CombinationPokemonRequirement.Some(
new MoveRequirement(EXTORTION_MOVES, true), new MoveRequirement(EXTORTION_MOVES, true),
new AbilityRequirement(EXTORTION_ABILITIES, true) new AbilityRequirement(EXTORTION_ABILITIES, true),
) ),
) )
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
@ -163,7 +171,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -180,6 +188,6 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp // Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .build();

View File

@ -1,6 +1,5 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { import {
generateModifierType, generateModifierType,
generateModifierTypeOption, generateModifierTypeOption,
@ -8,18 +7,12 @@ import {
initBattleWithEnemyConfig, initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, leaveEncounterWithoutBattle,
setEncounterExp, setEncounterExp,
setEncounterRewards setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
BerryModifierType, import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
ModifierTypeOption } from "#app/modifier/modifier-type";
import {
ModifierPoolType,
modifierTypes,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-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 { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; 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 PokemonData from "#app/system/pokemon-data";
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier } from "#app/modifier/modifier";
import i18next from "#app/plugins/i18n"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3810 | GitHub Issue #3810}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const BerriesAboundEncounter: MysteryEncounter = export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BERRIES_ABOUND) MysteryEncounterType.BERRIES_ABOUND,
)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withCatchAllowed(true) .withCatchAllowed(true)
@ -68,21 +68,27 @@ export const BerriesAboundEncounter: MysteryEncounter =
const bossPokemon = getRandomEncounterSpecies(level, true); const bossPokemon = getRandomEncounterSpecies(level, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
pokemonConfigs: [{ pokemonConfigs: [
{
level: level, level: level,
species: bossPokemon.species, species: bossPokemon.species,
dataSource: new PokemonData(bossPokemon), dataSource: new PokemonData(bossPokemon),
isBoss: true isBoss: true,
}], },
],
}; };
encounter.enemyPartyConfigs = [config]; encounter.enemyPartyConfigs = [config];
// Calculate the number of extra berries that player receives // Calculate the number of extra berries that player receives
// 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7 // 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7
const numBerries = const numBerries =
globalScene.currentBattle.waveIndex > 160 ? 7 globalScene.currentBattle.waveIndex > 160
: globalScene.currentBattle.waveIndex > 120 ? 5 ? 7
: globalScene.currentBattle.waveIndex > 40 ? 4 : 2; : globalScene.currentBattle.waveIndex > 120
? 5
: globalScene.currentBattle.waveIndex > 40
? 4
: 2;
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0); regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
encounter.misc = { numBerries }; encounter.misc = { numBerries };
@ -95,7 +101,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
y: -6, y: -6,
yShadow: -7, yShadow: -7,
disableAnimation: true, disableAnimation: true,
hasShadow: true hasShadow: true,
}, },
{ {
spriteKey: spriteKey, spriteKey: spriteKey,
@ -106,8 +112,8 @@ export const BerriesAboundEncounter: MysteryEncounter =
repeat: true, repeat: true,
isPokemon: true, isPokemon: true,
isShiny: bossPokemon.shiny, isShiny: bossPokemon.shiny,
variant: bossPokemon.variant variant: bossPokemon.variant,
} },
]; ];
// Get fastest party pokemon for option 2 // Get fastest party pokemon for option 2
@ -141,7 +147,12 @@ export const BerriesAboundEncounter: MysteryEncounter =
const berryText = i18next.t(`${namespace}:berries`); const berryText = i18next.t(`${namespace}:berries`);
globalScene.playSound("item_fanfare"); 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 // Generate a random berry and give it to the first Pokemon with room for it
for (let i = 0; i < numBerries; i++) { 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]); await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
} },
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip` buttonTooltip: `${namespace}:option.2.tooltip`,
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
// Pick race for berries // Pick race for berries
@ -192,7 +206,12 @@ export const BerriesAboundEncounter: MysteryEncounter =
const berryText = i18next.t(`${namespace}:berries`); const berryText = i18next.t(`${namespace}:berries`);
globalScene.playSound("item_fanfare"); 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 // Generate a random berry and give it to the first Pokemon with room for it
for (let i = 0; i < numBerries; i++) { 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 // 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 ? const statChangesForBattle: (
[ Stat.DEF, Stat.SPDEF, Stat.SPD ] : | Stat.ATK
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; | 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]; const config = globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.2.boss_enraged`); 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 showEncounterText(`${namespace}:option.2.selected_bad`);
await initBattleWithEnemyConfig(config); await initBattleWithEnemyConfig(config);
return; 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 // 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); const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1) / 0.08), numBerries), 2);
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed)); encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
@ -223,7 +260,12 @@ export const BerriesAboundEncounter: MysteryEncounter =
const berryText = i18next.t(`${namespace}:berries`); const berryText = i18next.t(`${namespace}:berries`);
globalScene.playSound("item_fanfare"); 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) // 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++) { 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); 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`); await showEncounterText(`${namespace}:option.2.selected`);
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -253,20 +301,25 @@ export const BerriesAboundEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp // Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .build();
function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) { 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 berry = generateModifierType(modifierTypes.BERRY, [berryType]) as BerryModifierType;
const party = globalScene.getPlayerParty(); const party = globalScene.getPlayerParty();
// Will try to apply to prioritized pokemon first, then do normal application method if it fails // Will try to apply to prioritized pokemon first, then do normal application method if it fails
if (prioritizedPokemon) { if (prioritizedPokemon) {
const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier const heldBerriesOfType = globalScene.findModifier(
&& m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier; m =>
m instanceof BerryModifier &&
m.pokemonId === prioritizedPokemon.id &&
(m as BerryModifier).berryType === berryType,
true,
) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry); applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry);
@ -276,8 +329,10 @@ function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
// Iterate over the party until berry was successfully given // Iterate over the party until berry was successfully given
for (const pokemon of party) { for (const pokemon of party) {
const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier const heldBerriesOfType = globalScene.findModifier(
&& m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier; m => m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType,
true,
) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
applyModifierTypeToPlayerPokemon(pokemon, berry); applyModifierTypeToPlayerPokemon(pokemon, berry);

View File

@ -1,5 +1,4 @@
import type { import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { import {
generateModifierType, generateModifierType,
generateModifierTypeOption, generateModifierTypeOption,
@ -39,20 +38,18 @@ import {
AttackTypeBoosterHeldItemTypeRequirement, AttackTypeBoosterHeldItemTypeRequirement,
CombinationPokemonRequirement, CombinationPokemonRequirement,
HeldItemRequirement, HeldItemRequirement,
TypeRequirement TypeRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements"; } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import type { import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
PokemonHeldItemModifier
} from "#app/modifier/modifier";
import { import {
AttackTypeBoosterModifier, AttackTypeBoosterModifier,
BypassSpeedChanceModifier, BypassSpeedChanceModifier,
ContactHeldItemTransferChanceModifier, ContactHeldItemTransferChanceModifier,
GigantamaxAccessModifier, GigantamaxAccessModifier,
MegaEvolutionAccessModifier MegaEvolutionAccessModifier,
} from "#app/modifier/modifier"; } from "#app/modifier/modifier";
import i18next from "i18next"; import i18next from "i18next";
import MoveInfoOverlay from "#app/ui/move-info-overlay"; import MoveInfoOverlay from "#app/ui/move-info-overlay";
@ -87,7 +84,7 @@ const POOL_1_POKEMON = [
Species.CHARJABUG, Species.CHARJABUG,
Species.RIBOMBEE, Species.RIBOMBEE,
Species.SPIDOPS, Species.SPIDOPS,
Species.LOKIX Species.LOKIX,
]; ];
const POOL_2_POKEMON = [ const POOL_2_POKEMON = [
@ -116,26 +113,26 @@ const POOL_2_POKEMON = [
Species.KLEAVOR, Species.KLEAVOR,
]; ];
const POOL_3_POKEMON: { species: Species, formIndex?: number }[] = [ const POOL_3_POKEMON: { species: Species; formIndex?: number }[] = [
{ {
species: Species.PINSIR, species: Species.PINSIR,
formIndex: 1 formIndex: 1,
}, },
{ {
species: Species.SCIZOR, species: Species.SCIZOR,
formIndex: 1 formIndex: 1,
}, },
{ {
species: Species.HERACROSS, species: Species.HERACROSS,
formIndex: 1 formIndex: 1,
}, },
{ {
species: Species.ORBEETLE, species: Species.ORBEETLE,
formIndex: 1 formIndex: 1,
}, },
{ {
species: Species.CENTISKORCH, species: Species.CENTISKORCH,
formIndex: 1 formIndex: 1,
}, },
{ {
species: Species.DURANT, species: Species.DURANT,
@ -148,35 +145,19 @@ const POOL_3_POKEMON: { species: Species, formIndex?: number }[] = [
}, },
]; ];
const POOL_4_POKEMON = [ const POOL_4_POKEMON = [Species.GENESECT, Species.SLITHER_WING, Species.BUZZWOLE, Species.PHEROMOSA];
Species.GENESECT,
Species.SLITHER_WING,
Species.BUZZWOLE,
Species.PHEROMOSA
];
const PHYSICAL_TUTOR_MOVES = [ const PHYSICAL_TUTOR_MOVES = [
Moves.MEGAHORN, Moves.MEGAHORN,
Moves.X_SCISSOR, Moves.X_SCISSOR,
Moves.ATTACK_ORDER, Moves.ATTACK_ORDER,
Moves.PIN_MISSILE, Moves.PIN_MISSILE,
Moves.FIRST_IMPRESSION Moves.FIRST_IMPRESSION,
]; ];
const SPECIAL_TUTOR_MOVES = [ const SPECIAL_TUTOR_MOVES = [Moves.SILVER_WIND, Moves.BUG_BUZZ, Moves.SIGNAL_BEAM, Moves.POLLEN_PUFF];
Moves.SILVER_WIND,
Moves.BUG_BUZZ,
Moves.SIGNAL_BEAM,
Moves.POLLEN_PUFF
];
const STATUS_TUTOR_MOVES = [ const STATUS_TUTOR_MOVES = [Moves.STRING_SHOT, Moves.STICKY_WEB, Moves.SILK_TRAP, Moves.RAGE_POWDER, Moves.HEAL_ORDER];
Moves.STRING_SHOT,
Moves.STICKY_WEB,
Moves.SILK_TRAP,
Moves.RAGE_POWDER,
Moves.HEAL_ORDER
];
const MISC_TUTOR_MOVES = [ const MISC_TUTOR_MOVES = [
Moves.BUG_BITE, Moves.BUG_BITE,
@ -185,7 +166,7 @@ const MISC_TUTOR_MOVES = [
Moves.QUIVER_DANCE, Moves.QUIVER_DANCE,
Moves.TAIL_GLOW, Moves.TAIL_GLOW,
Moves.INFESTATION, 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3820 | GitHub Issue #3820}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const BugTypeSuperfanEncounter: MysteryEncounter = export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BUG_TYPE_SUPERFAN) MysteryEncounterType.BUG_TYPE_SUPERFAN,
)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withPrimaryPokemonRequirement( .withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some( CombinationPokemonRequirement.Some(
// Must have at least 1 Bug type on team, OR have a bug item somewhere on the team // Must have at least 1 Bug type on team, OR have a bug item somewhere on the team
new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1), new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1),
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1), new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1),
new TypeRequirement(PokemonType.BUG, false, 1) new TypeRequirement(PokemonType.BUG, false, 1),
) ),
) )
.withMaxAllowedEncounters(1) .withMaxAllowedEncounters(1)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
@ -234,7 +216,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
female: true, 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]) { if (globalScene.currentBattle.waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) {
beedrillKeys = getSpriteKeysFromSpecies(Species.BEEDRILL, false); beedrillKeys = getSpriteKeysFromSpecies(Species.BEEDRILL, false);
butterfreeKeys = getSpriteKeysFromSpecies(Species.BUTTERFREE, false); butterfreeKeys = getSpriteKeysFromSpecies(Species.BUTTERFREE, false);
@ -254,7 +236,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
x: -30, x: -30,
tint: 0.15, tint: 0.15,
y: -4, y: -4,
yShadow: -4 yShadow: -4,
}, },
{ {
spriteKey: butterfreeKeys.spriteKey, spriteKey: butterfreeKeys.spriteKey,
@ -265,7 +247,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
x: 30, x: 30,
tint: 0.15, tint: 0.15,
y: -4, y: -4,
yShadow: -4 yShadow: -4,
}, },
{ {
spriteKey: spriteKey, spriteKey: spriteKey,
@ -273,7 +255,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
hasShadow: true, hasShadow: true,
x: 4, x: 4,
y: 7, y: 7,
yShadow: 7 yShadow: 7,
}, },
]; ];
@ -315,7 +297,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
moveTutorOptions.push(new PokemonMove(STATUS_TUTOR_MOVES[randSeedInt(STATUS_TUTOR_MOVES.length)])); moveTutorOptions.push(new PokemonMove(STATUS_TUTOR_MOVES[randSeedInt(STATUS_TUTOR_MOVES.length)]));
moveTutorOptions.push(new PokemonMove(MISC_TUTOR_MOVES[randSeedInt(MISC_TUTOR_MOVES.length)])); moveTutorOptions.push(new PokemonMove(MISC_TUTOR_MOVES[randSeedInt(MISC_TUTOR_MOVES.length)]));
encounter.misc = { encounter.misc = {
moveTutorOptions moveTutorOptions,
}; };
// Assigns callback that teaches move before continuing to rewards // Assigns callback that teaches move before continuing to rewards
@ -324,15 +306,15 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
setEncounterRewards({ fillRemaining: true }); setEncounterRewards({ fillRemaining: true });
await transitionMysteryEncounterIntroVisuals(true, true); await transitionMysteryEncounterIntroVisuals(true, true);
await initBattleWithEnemyConfig(config); await initBattleWithEnemyConfig(config);
} },
) )
.withOption(MysteryEncounterOptionBuilder .withOption(
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new TypeRequirement(PokemonType.BUG, false, 1)) // Must have 1 Bug type on team .withPrimaryPokemonRequirement(new TypeRequirement(PokemonType.BUG, false, 1)) // Must have 1 Bug type on team
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip` disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
}) })
.withPreOptionPhase(async () => { .withPreOptionPhase(async () => {
// Player shows off their bug types // Player shows off their bug types
@ -340,11 +322,16 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
// Player gets different rewards depending on the number of bug types they have // Player gets different rewards depending on the number of bug types they have
const numBugTypes = globalScene.getPlayerParty().filter(p => p.isOfType(PokemonType.BUG, true)).length; const numBugTypes = globalScene.getPlayerParty().filter(p => p.isOfType(PokemonType.BUG, true)).length;
const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, { count: numBugTypes }); const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, {
count: numBugTypes,
});
encounter.setDialogueToken("numBugTypes", numBugTypesText); encounter.setDialogueToken("numBugTypes", numBugTypesText);
if (numBugTypes < 2) { 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 = [ encounter.selectedOption!.dialogue!.selected = [
{ {
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
@ -352,7 +339,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
}, },
]; ];
} else if (numBugTypes < 4) { } 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 = [ encounter.selectedOption!.dialogue!.selected = [
{ {
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
@ -360,7 +350,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
}, },
]; ];
} else if (numBugTypes < 6) { } 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 = [ encounter.selectedOption!.dialogue!.selected = [
{ {
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
@ -399,7 +392,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
modifierOptions.push(specialOptions[randSeedInt(specialOptions.length)]); modifierOptions.push(specialOptions[randSeedInt(specialOptions.length)]);
} }
setEncounterRewards({ guaranteedModifierTypeOptions: modifierOptions, fillRemaining: false }); setEncounterRewards({
guaranteedModifierTypeOptions: modifierOptions,
fillRemaining: false,
});
encounter.selectedOption!.dialogue!.selected = [ encounter.selectedOption!.dialogue!.selected = [
{ {
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
@ -412,15 +408,16 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
// Player shows off their bug types // Player shows off their bug types
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}) })
.build()) .build(),
.withOption(MysteryEncounterOptionBuilder )
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement( .withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some( CombinationPokemonRequirement.Some(
// Meets one or both of the below reqs // Meets one or both of the below reqs
new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1), new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1),
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1) new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1),
) ),
) )
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
@ -443,10 +440,13 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
const onPokemonSelected = (pokemon: PlayerPokemon) => { const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones // Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter(item => { const validItems = pokemon.getHeldItems().filter(item => {
return (item instanceof BypassSpeedChanceModifier || return (
(item instanceof BypassSpeedChanceModifier ||
item instanceof ContactHeldItemTransferChanceModifier || item instanceof ContactHeldItemTransferChanceModifier ||
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)) && (item instanceof AttackTypeBoosterModifier &&
item.isTransferable; (item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)) &&
item.isTransferable
);
}); });
return validItems.map((modifier: PokemonHeldItemModifier) => { return validItems.map((modifier: PokemonHeldItemModifier) => {
@ -469,9 +469,12 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
// If pokemon has valid item, it can be selected // If pokemon has valid item, it can be selected
const hasValidItem = pokemon.getHeldItems().some(item => { const hasValidItem = pokemon.getHeldItems().some(item => {
return item instanceof BypassSpeedChanceModifier || return (
item instanceof BypassSpeedChanceModifier ||
item instanceof ContactHeldItemTransferChanceModifier || item instanceof ContactHeldItemTransferChanceModifier ||
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG); (item instanceof AttackTypeBoosterModifier &&
(item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)
);
}); });
if (!hasValidItem) { if (!hasValidItem) {
return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null; 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)!; const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!;
bugNet.type.tier = ModifierTier.ROGUE; 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); leaveEncounterWithoutBattle(true);
}) })
.build()) .build(),
)
.withOutroDialogue([ .withOutroDialogue([
{ {
text: `${namespace}:outro`, text: `${namespace}:outro`,
@ -542,108 +550,160 @@ function getTrainerConfigForWave(waveIndex: number) {
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[4]) { } else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[4]) {
config config
.setPartyTemplates(new TrainerPartyTemplate(5, PartyMemberStrength.AVERAGE)) .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.formIndex = 1;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
})) }),
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true, p => { )
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1; p.formIndex = 1;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
})) }),
)
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true)) .setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
.setPartyMemberFunc(3, 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)) { if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex; p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }
})); }),
);
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[5]) { } else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[5]) {
pool3Copy = randSeedShuffle(pool3Copy); pool3Copy = randSeedShuffle(pool3Copy);
const pool3Mon2 = pool3Copy.pop()!; const pool3Mon2 = pool3Copy.pop()!;
config config
.setPartyTemplates(new TrainerPartyTemplate(5, PartyMemberStrength.AVERAGE)) .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.formIndex = 1;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
})) }),
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true, p => { )
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1; p.formIndex = 1;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
})) }),
)
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true)) .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)) { if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex; p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }
})) }),
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ pool3Mon2.species ], TrainerSlot.TRAINER, true, p => { )
.setPartyMemberFunc(
4,
getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon2.formIndex)) { if (!isNullOrUndefined(pool3Mon2.formIndex)) {
p.formIndex = pool3Mon2.formIndex; p.formIndex = pool3Mon2.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }
})); }),
);
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[6]) { } else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[6]) {
config config
.setPartyTemplates(new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(4, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(1, PartyMemberStrength.STRONG))) .setPartyTemplates(
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true, p => { 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.formIndex = 1;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
})) }),
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true, p => { )
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1; p.formIndex = 1;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
})) }),
)
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true)) .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)) { if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex; p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }
})) }),
)
.setPartyMemberFunc(4, getRandomPartyMemberFunc(POOL_4_POKEMON, TrainerSlot.TRAINER, true)); .setPartyMemberFunc(4, getRandomPartyMemberFunc(POOL_4_POKEMON, TrainerSlot.TRAINER, true));
} else { } else {
pool3Copy = randSeedShuffle(pool3Copy); pool3Copy = randSeedShuffle(pool3Copy);
const pool3Mon2 = pool3Copy.pop()!; const pool3Mon2 = pool3Copy.pop()!;
config config
.setPartyTemplates(new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(4, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(1, PartyMemberStrength.STRONG))) .setPartyTemplates(
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true, p => { 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.setBoss(true, 2);
p.formIndex = 1; p.formIndex = 1;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); 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.setBoss(true, 2);
p.formIndex = 1; p.formIndex = 1;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
})) }),
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ pool3Mon.species ], TrainerSlot.TRAINER, true, p => { )
.setPartyMemberFunc(
2,
getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon.formIndex)) { if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex; p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }
})) }),
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ pool3Mon2.species ], TrainerSlot.TRAINER, true, p => { )
.setPartyMemberFunc(
3,
getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon2.formIndex)) { if (!isNullOrUndefined(pool3Mon2.formIndex)) {
p.formIndex = pool3Mon2.formIndex; p.formIndex = pool3Mon2.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }
})) }),
)
.setPartyMemberFunc(4, getRandomPartyMemberFunc(POOL_4_POKEMON, TrainerSlot.TRAINER, true)); .setPartyMemberFunc(4, getRandomPartyMemberFunc(POOL_4_POKEMON, TrainerSlot.TRAINER, true));
} }
@ -651,6 +711,7 @@ function getTrainerConfigForWave(waveIndex: number) {
} }
function doBugTypeMoveTutor(): Promise<void> { function doBugTypeMoveTutor(): Promise<void> {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO explain
return new Promise<void>(async resolve => { return new Promise<void>(async resolve => {
const moveOptions = globalScene.currentBattle.mysteryEncounter!.misc.moveTutorOptions; const moveOptions = globalScene.currentBattle.mysteryEncounter!.misc.moveTutorOptions;
await showEncounterDialogue(`${namespace}:battle_won`, `${namespace}:speaker`); await showEncounterDialogue(`${namespace}:battle_won`, `${namespace}:speaker`);
@ -663,7 +724,7 @@ function doBugTypeMoveTutor(): Promise<void> {
right: true, right: true,
x: 1, x: 1,
y: -MoveInfoOverlay.getHeight(overlayScale, true) - 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); globalScene.ui.add(moveInfoOverlay);
@ -688,7 +749,12 @@ function doBugTypeMoveTutor(): Promise<void> {
moveInfoOverlay.setVisible(false); 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; // let forceExit = !!result;
if (!result) { if (!result) {
moveInfoOverlay.active = false; moveInfoOverlay.active = false;
@ -699,7 +765,9 @@ function doBugTypeMoveTutor(): Promise<void> {
// Option select complete, handle if they are learning a move // Option select complete, handle if they are learning a move
if (result && result.selectedOptionIndex < moveOptions.length) { 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 // 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 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 {
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config"; 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 { ModifierTier } from "#app/modifier/modifier-tier";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type"; import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type";
@ -14,7 +22,10 @@ import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { applyAbilityOverrideToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import {
applyAbilityOverrideToPokemon,
applyModifierTypeToPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -55,7 +66,7 @@ const RANDOM_ABILITY_POOL = [
Abilities.MISTY_SURGE, Abilities.MISTY_SURGE,
Abilities.MAGICIAN, Abilities.MAGICIAN,
Abilities.SHEER_FORCE, 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3807 | GitHub Issue #3807}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ClowningAroundEncounter: MysteryEncounter = export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND) MysteryEncounterType.CLOWNING_AROUND,
)
.withEncounterTier(MysteryEncounterTier.ULTRA) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withDisallowedChallenges(Challenges.SINGLE_TYPE) .withDisallowedChallenges(Challenges.SINGLE_TYPE)
.withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
@ -79,7 +91,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
x: -25, x: -25,
tint: 0.3, tint: 0.3,
y: -3, y: -3,
yShadow: -3 yShadow: -3,
}, },
{ {
spriteKey: Species.BLACEPHALON.toString(), spriteKey: Species.BLACEPHALON.toString(),
@ -89,7 +101,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
x: 25, x: 25,
tint: 0.3, tint: 0.3,
y: -3, y: -3,
yShadow: -3 yShadow: -3,
}, },
{ {
spriteKey: "harlequin", spriteKey: "harlequin",
@ -97,7 +109,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
hasShadow: true, hasShadow: true,
x: 0, x: 0,
y: 2, y: 2,
yShadow: 2 yShadow: 2,
}, },
]) ])
.withIntroDialogue([ .withIntroDialogue([
@ -106,7 +118,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
}, },
{ {
text: `${namespace}:intro_dialogue`, text: `${namespace}:intro_dialogue`,
speaker: `${namespace}:speaker` speaker: `${namespace}:speaker`,
}, },
]) ])
.withOnInit(() => { .withOnInit(() => {
@ -116,7 +128,8 @@ export const ClowningAroundEncounter: MysteryEncounter =
const clownConfig = trainerConfigs[clownTrainerType].clone(); const clownConfig = trainerConfigs[clownTrainerType].clone();
const clownPartyTemplate = new TrainerPartyCompoundTemplate( const clownPartyTemplate = new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG), new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER)); new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
);
clownConfig.setPartyTemplates(clownPartyTemplate); clownConfig.setPartyTemplates(clownPartyTemplate);
clownConfig.setDoubleOnly(); clownConfig.setDoubleOnly();
// @ts-ignore // @ts-ignore
@ -136,20 +149,25 @@ export const ClowningAroundEncounter: MysteryEncounter =
encounter.enemyPartyConfigs.push({ encounter.enemyPartyConfigs.push({
trainerConfig: clownConfig, 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), species: getPokemonSpecies(Species.MR_MIME),
isBoss: true, 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), species: getPokemonSpecies(Species.BLACEPHALON),
customPokemonData: new CustomPokemonData({ ability: ability, types: [ firstType, secondType ]}), customPokemonData: new CustomPokemonData({
ability: ability,
types: [firstType, secondType],
}),
isBoss: true, 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 // Load animations/sfx for start of fight moves
@ -164,15 +182,14 @@ export const ClowningAroundEncounter: MysteryEncounter =
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.1.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 // TODO: when Magic Room and Wonder Room are implemented, add those to start of battle
encounter.startOfBattleEffects.push( encounter.startOfBattleEffects.push(
{ // Mr. Mime copies the Blacephalon's random ability {
// Mr. Mime copies the Blacephalon's random ability
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY_2], targets: [BattlerIndex.ENEMY_2],
move: new PokemonMove(Moves.ROLE_PLAY), move: new PokemonMove(Moves.ROLE_PLAY),
ignorePp: true ignorePp: true,
}, },
{ {
sourceBattlerIndex: BattlerIndex.ENEMY_2, sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.TAUNT), move: new PokemonMove(Moves.TAUNT),
ignorePp: true ignorePp: true,
}, },
{ {
sourceBattlerIndex: BattlerIndex.ENEMY_2, sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER_2], targets: [BattlerIndex.PLAYER_2],
move: new PokemonMove(Moves.TAUNT), move: new PokemonMove(Moves.TAUNT),
ignorePp: true ignorePp: true,
}); },
);
await transitionMysteryEncounterIntroVisuals(); await transitionMysteryEncounterIntroVisuals();
await initBattleWithEnemyConfig(config); await initBattleWithEnemyConfig(config);
@ -222,31 +241,34 @@ export const ClowningAroundEncounter: MysteryEncounter =
y: "-=16", y: "-=16",
alpha: 0, alpha: 0,
ease: "Sine.easeInOut", 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); background.playWithoutTargets(230, 40, 2);
return true; return true;
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.2.selected`, text: `${namespace}:option.2.selected`,
speaker: `${namespace}:speaker` speaker: `${namespace}:speaker`,
}, },
{ {
text: `${namespace}:option.2.selected_2`, text: `${namespace}:option.2.selected_2`,
}, },
{ {
text: `${namespace}:option.2.selected_3`, text: `${namespace}:option.2.selected_3`,
speaker: `${namespace}:speaker` speaker: `${namespace}:speaker`,
}, },
], ],
}) })
@ -258,19 +280,21 @@ export const ClowningAroundEncounter: MysteryEncounter =
const party = globalScene.getPlayerParty(); const party = globalScene.getPlayerParty();
let mostHeldItemsPokemon = party[0]; let mostHeldItemsPokemon = party[0];
let count = mostHeldItemsPokemon.getHeldItems() let count = mostHeldItemsPokemon
.getHeldItems()
.filter(m => m.isTransferable && !(m instanceof BerryModifier)) .filter(m => m.isTransferable && !(m instanceof BerryModifier))
.reduce((v, m) => v + m.stackCount, 0); .reduce((v, m) => v + m.stackCount, 0);
party.forEach(pokemon => { for (const pokemon of party) {
const nextCount = pokemon.getHeldItems() const nextCount = pokemon
.getHeldItems()
.filter(m => m.isTransferable && !(m instanceof BerryModifier)) .filter(m => m.isTransferable && !(m instanceof BerryModifier))
.reduce((v, m) => v + m.stackCount, 0); .reduce((v, m) => v + m.stackCount, 0);
if (nextCount > count) { if (nextCount > count) {
mostHeldItemsPokemon = pokemon; mostHeldItemsPokemon = pokemon;
count = nextCount; count = nextCount;
} }
}); }
encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender()); encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender());
@ -278,11 +302,10 @@ export const ClowningAroundEncounter: MysteryEncounter =
// Shuffles Berries (if they have any) // Shuffles Berries (if they have any)
let numBerries = 0; let numBerries = 0;
items.filter(m => m instanceof BerryModifier) for (const m of items.filter(m => m instanceof BerryModifier)) {
.forEach(m => {
numBerries += m.stackCount; numBerries += m.stackCount;
globalScene.removeModifier(m); globalScene.removeModifier(m);
}); }
generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries"); generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries");
@ -291,8 +314,8 @@ export const ClowningAroundEncounter: MysteryEncounter =
// And Golden Eggs as Rogue tier // And Golden Eggs as Rogue tier
let numUltra = 0; let numUltra = 0;
let numRogue = 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 type = m.type.withTierFromPool(ModifierPoolType.PLAYER, party);
const tier = type.tier ?? ModifierTier.ULTRA; const tier = type.tier ?? ModifierTier.ULTRA;
if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) { if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
@ -302,7 +325,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
numUltra += m.stackCount; numUltra += m.stackCount;
globalScene.removeModifier(m); globalScene.removeModifier(m);
} }
}); }
generateItemsOfTier(mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA); generateItemsOfTier(mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE); generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
@ -312,29 +335,32 @@ export const ClowningAroundEncounter: MysteryEncounter =
}) })
.withPostOptionPhase(async () => { .withPostOptionPhase(async () => {
// Play animations // 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); background.playWithoutTargets(230, 40, 2);
await transitionMysteryEncounterIntroVisuals(true, true, 200); await transitionMysteryEncounterIntroVisuals(true, true, 200);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.3.selected`, text: `${namespace}:option.3.selected`,
speaker: `${namespace}:speaker` speaker: `${namespace}:speaker`,
}, },
{ {
text: `${namespace}:option.3.selected_2`, text: `${namespace}:option.3.selected_2`,
}, },
{ {
text: `${namespace}:option.3.selected_3`, text: `${namespace}:option.3.selected_3`,
speaker: `${namespace}:speaker` speaker: `${namespace}:speaker`,
}, },
], ],
}) })
@ -347,7 +373,10 @@ 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 // 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 // Makes the "randomness" of the shuffle slightly less punishing
let priorityTypes = pokemon.moveset 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); .map(move => move!.getMove().type);
if (priorityTypes?.length > 0) { if (priorityTypes?.length > 0) {
priorityTypes = [...new Set(priorityTypes)].sort(); priorityTypes = [...new Set(priorityTypes)].sort();
@ -383,11 +412,15 @@ export const ClowningAroundEncounter: MysteryEncounter =
}) })
.withPostOptionPhase(async () => { .withPostOptionPhase(async () => {
// Play animations // 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); background.playWithoutTargets(230, 40, 2);
await transitionMysteryEncounterIntroVisuals(true, true, 200); await transitionMysteryEncounterIntroVisuals(true, true, 200);
}) })
.build() .build(),
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
@ -397,6 +430,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
.build(); .build();
async function handleSwapAbility() { async function handleSwapAbility() {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
return new Promise<boolean>(async resolve => { return new Promise<boolean>(async resolve => {
await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`); await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`);
await showEncounterText(`${namespace}:option.1.apply_ability_message`); await showEncounterText(`${namespace}:option.1.apply_ability_message`);
@ -415,21 +449,21 @@ function displayYesNoOptions(resolve) {
handler: () => { handler: () => {
onYesAbilitySwap(resolve); onYesAbilitySwap(resolve);
return true; return true;
} },
}, },
{ {
label: i18next.t("menu:no"), label: i18next.t("menu:no"),
handler: () => { handler: () => {
resolve(false); resolve(false);
return true; return true;
} },
} },
]; ];
const config: OptionSelectConfig = { const config: OptionSelectConfig = {
options: fullOptions, options: fullOptions,
maxOptions: 7, maxOptions: 7,
yOffset: 0 yOffset: 0,
}; };
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true); 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.GOLDEN_PUNCH, 5],
[modifierTypes.ATTACK_TYPE_BOOSTER, 99], [modifierTypes.ATTACK_TYPE_BOOSTER, 99],
[modifierTypes.QUICK_CLAW, 3], [modifierTypes.QUICK_CLAW, 3],
[ modifierTypes.WIDE_LENS, 3 ] [modifierTypes.WIDE_LENS, 3],
]; ];
const roguePool = [ const roguePool = [
@ -473,7 +507,7 @@ function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: Mod
[modifierTypes.BATON, 1], [modifierTypes.BATON, 1],
[modifierTypes.FOCUS_BAND, 5], [modifierTypes.FOCUS_BAND, 5],
[modifierTypes.KINGS_ROCK, 3], [modifierTypes.KINGS_ROCK, 3],
[ modifierTypes.GRIP_CLAW, 5 ] [modifierTypes.GRIP_CLAW, 5],
]; ];
const berryPool = [ const berryPool = [
@ -487,7 +521,7 @@ function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: Mod
[BerryType.PETAYA, 3], [BerryType.PETAYA, 3],
[BerryType.SALAC, 2], [BerryType.SALAC, 2],
[BerryType.SITRUS, 2], [BerryType.SITRUS, 2],
[ BerryType.STARF, 3 ] [BerryType.STARF, 3],
]; ];
let pool: any[]; 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 { DANCING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; 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 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 {
import { catchPokemon, getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; 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 { getPokemonSpecies } from "#app/data/pokemon-species";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#app/data/trainer-config";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
@ -44,7 +53,7 @@ const BAILE_STYLE_BIOMES = [
Biome.WASTELAND, Biome.WASTELAND,
Biome.MOUNTAIN, Biome.MOUNTAIN,
Biome.BADLANDS, Biome.BADLANDS,
Biome.DESERT Biome.DESERT,
]; ];
// Electric form // Electric form
@ -55,7 +64,7 @@ const POM_POM_STYLE_BIOMES = [
Biome.LABORATORY, Biome.LABORATORY,
Biome.SLUM, Biome.SLUM,
Biome.METROPOLIS, Biome.METROPOLIS,
Biome.DOJO Biome.DOJO,
]; ];
// Psychic form // Psychic form
@ -66,7 +75,7 @@ const PAU_STYLE_BIOMES = [
Biome.PLAINS, Biome.PLAINS,
Biome.GRASS, Biome.GRASS,
Biome.TALL_GRASS, Biome.TALL_GRASS,
Biome.FOREST Biome.FOREST,
]; ];
// Ghost form // Ghost form
@ -77,7 +86,7 @@ const SENSU_STYLE_BIOMES = [
Biome.ABYSS, Biome.ABYSS,
Biome.GRAVEYARD, Biome.GRAVEYARD,
Biome.LAKE, 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3823 | GitHub Issue #3823}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const DancingLessonsEncounter: MysteryEncounter = export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DANCING_LESSONS) MysteryEncounterType.DANCING_LESSONS,
)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // Uses a real Pokemon sprite instead of ME Intro Visuals .withIntroSpriteConfigs([]) // Uses a real Pokemon sprite instead of ME Intro Visuals
@ -108,7 +118,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}:intro`, text: `${namespace}:intro`,
} },
]) ])
.setLocalizationKey(`${namespace}`) .setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
@ -147,9 +157,9 @@ export const DancingLessonsEncounter: MysteryEncounter =
const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData); const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
// Adds a real Pokemon sprite to the field (required for the animation) // 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); enemyPokemon.leaveField(true, true, true);
}); }
globalScene.currentBattle.enemyParty = [oricorio]; globalScene.currentBattle.enemyParty = [oricorio];
globalScene.field.add(oricorio); globalScene.field.add(oricorio);
// Spawns on offscreen field // Spawns on offscreen field
@ -157,7 +167,8 @@ export const DancingLessonsEncounter: MysteryEncounter =
encounter.loadAssets.push(oricorio.loadAssets()); encounter.loadAssets.push(oricorio.loadAssets());
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
pokemonConfigs: [{ pokemonConfigs: [
{
species: species, species: species,
dataSource: oricorioData, dataSource: oricorioData,
isBoss: true, isBoss: true,
@ -165,13 +176,21 @@ export const DancingLessonsEncounter: MysteryEncounter =
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.boss_enraged`); 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.enemyPartyConfigs = [config];
encounter.misc = { encounter.misc = {
oricorioData oricorioData,
}; };
encounter.setDialogueToken("oricorioName", getPokemonSpecies(Species.ORICORIO).getName()); encounter.setDialogueToken("oricorioName", getPokemonSpecies(Species.ORICORIO).getName());
@ -179,8 +198,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
return true; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
@ -198,18 +216,20 @@ export const DancingLessonsEncounter: MysteryEncounter =
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.REVELATION_DANCE), move: new PokemonMove(Moves.REVELATION_DANCE),
ignorePp: true ignorePp: true,
}); });
await hideOricorioPokemon(); await hideOricorioPokemon();
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.BATON ], fillRemaining: true }); setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.BATON],
fillRemaining: true,
});
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
@ -225,10 +245,16 @@ export const DancingLessonsEncounter: MysteryEncounter =
const onPokemonSelected = (pokemon: PlayerPokemon) => { const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); 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 // 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(); danceAnim.play();
}; };
@ -239,11 +265,10 @@ export const DancingLessonsEncounter: MysteryEncounter =
await hideOricorioPokemon(); await hideOricorioPokemon();
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
@ -283,7 +308,11 @@ export const DancingLessonsEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
// If pokemon meets primary pokemon reqs, it can be selected // If pokemon meets primary pokemon reqs, it can be selected
if (!pokemon.isAllowedInBattle()) { 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); const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon);
if (!meetsReqs) { if (!meetsReqs) {
@ -315,7 +344,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
await catchPokemon(oricorio, null, PokeballType.POKEBALL, false); await catchPokemon(oricorio, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build() .build(),
) )
.build(); .build();
@ -332,7 +361,7 @@ function hideOricorioPokemon() {
onComplete: () => { onComplete: () => {
globalScene.field.remove(oricorioSprite, true); globalScene.field.remove(oricorioSprite, true);
resolve(); resolve();
} },
}); });
}); });
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,16 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { import {
getRandomEncounterSpecies, getRandomEncounterSpecies,
initBattleWithEnemyConfig, initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, leaveEncounterWithoutBattle,
setEncounterExp, setEncounterExp,
setEncounterRewards setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import type { import type { ModifierTypeOption } from "#app/modifier/modifier-type";
ModifierTypeOption } from "#app/modifier/modifier-type";
import { import {
getPlayerModifierTypeOptions, getPlayerModifierTypeOptions,
ModifierPoolType, 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 { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; 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 PokemonData from "#app/system/pokemon-data";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3795 | GitHub Issue #3795}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const FightOrFlightEncounter: MysteryEncounter = export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT) MysteryEncounterType.FIGHT_OR_FLIGHT,
)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withCatchAllowed(true) .withCatchAllowed(true)
@ -62,7 +65,8 @@ export const FightOrFlightEncounter: MysteryEncounter =
const bossPokemon = getRandomEncounterSpecies(level, true); const bossPokemon = getRandomEncounterSpecies(level, true);
encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender()); encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender());
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
pokemonConfigs: [{ pokemonConfigs: [
{
level: level, level: level,
species: bossPokemon.species, species: bossPokemon.species,
dataSource: new PokemonData(bossPokemon), dataSource: new PokemonData(bossPokemon),
@ -73,8 +77,9 @@ export const FightOrFlightEncounter: MysteryEncounter =
// Randomly boost 1 stat 2 stages // Randomly boost 1 stat 2 stages
// Cannot boost Spd, Acc, or Evasion // Cannot boost Spd, Acc, or Evasion
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2)); globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2));
} },
}], },
],
}; };
encounter.enemyPartyConfigs = [config]; encounter.enemyPartyConfigs = [config];
@ -92,7 +97,10 @@ export const FightOrFlightEncounter: MysteryEncounter =
let item: ModifierTypeOption | null = null; 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 // 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") { 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.setDialogueToken("itemName", item.type.name);
encounter.misc = item; encounter.misc = item;
@ -107,7 +115,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
y: -5, y: -5,
scale: 0.75, scale: 0.75,
isItem: true, isItem: true,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: spriteKey, spriteKey: spriteKey,
@ -118,7 +126,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
repeat: true, repeat: true,
isPokemon: true, isPokemon: true,
isShiny: bossPokemon.shiny, isShiny: bossPokemon.shiny,
variant: bossPokemon.variant variant: bossPokemon.variant,
}, },
]; ];
@ -142,13 +150,15 @@ export const FightOrFlightEncounter: MysteryEncounter =
// Pick battle // Pick battle
// Pokemon will randomly boost 1 stat by 2 stages // Pokemon will randomly boost 1 stat by 2 stages
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption; 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]); await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
} },
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
@ -156,22 +166,25 @@ export const FightOrFlightEncounter: MysteryEncounter =
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`, disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.2.selected` text: `${namespace}:option.2.selected`,
} },
] ],
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
// Pick steal // Pick steal
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption; const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false }); setEncounterRewards({
guaranteedModifierTypeOptions: [item],
fillRemaining: false,
});
// Use primaryPokemon to execute the thievery // Use primaryPokemon to execute the thievery
const primaryPokemon = encounter.options[1].primaryPokemon!; const primaryPokemon = encounter.options[1].primaryPokemon!;
setEncounterExp(primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp); setEncounterExp(primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -187,6 +200,6 @@ export const FightOrFlightEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp // Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3819 | GitHub Issue #3819}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const FunAndGamesEncounter: MysteryEncounter = export const FunAndGamesEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FUN_AND_GAMES) MysteryEncounterType.FUN_AND_GAMES,
)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion to play .withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion to play
@ -60,7 +67,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
hasShadow: true, hasShadow: true,
x: -28, x: -28,
y: 6, y: 6,
yShadow: 6 yShadow: 6,
}, },
{ {
spriteKey: "fun_and_games_man", spriteKey: "fun_and_games_man",
@ -68,7 +75,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
hasShadow: true, hasShadow: true,
x: 40, x: 40,
y: 6, y: 6,
yShadow: 6 yShadow: 6,
}, },
]) ])
.withIntroDialogue([ .withIntroDialogue([
@ -91,8 +98,8 @@ export const FunAndGamesEncounter: MysteryEncounter =
globalScene.fadeAndSwitchBgm("mystery_encounter_fun_and_games"); globalScene.fadeAndSwitchBgm("mystery_encounter_fun_and_games");
return true; return true;
}) })
.withOption(MysteryEncounterOptionBuilder .withOption(
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion .withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
@ -127,7 +134,11 @@ export const FunAndGamesEncounter: MysteryEncounter =
// Update money // Update money
const moneyCost = (encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney; const moneyCost = (encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney;
updatePlayerMoney(-moneyCost, true, false); 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 // Handlers for battle events
encounter.onTurnStart = handleNextTurn; // triggered during TurnInitPhase encounter.onTurnStart = handleNextTurn; // triggered during TurnInitPhase
@ -139,7 +150,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
return true; return true;
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -156,11 +167,12 @@ export const FunAndGamesEncounter: MysteryEncounter =
await transitionMysteryEncounterIntroVisuals(true, true); await transitionMysteryEncounterIntroVisuals(true, true);
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .build();
async function summonPlayerPokemon() { async function summonPlayerPokemon() {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
return new Promise<void>(async resolve => { return new Promise<void>(async resolve => {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -176,9 +188,15 @@ async function summonPlayerPokemon() {
// Do trainer summon animation // Do trainer summon animation
let playerAnimationPromise: Promise<void> | undefined; 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.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.time.delayedCall(562, () => {
globalScene.trainer.setFrame("2"); globalScene.trainer.setFrame("2");
globalScene.time.delayedCall(64, () => { globalScene.time.delayedCall(64, () => {
@ -189,7 +207,7 @@ async function summonPlayerPokemon() {
targets: globalScene.trainer, targets: globalScene.trainer,
x: -36, x: -36,
duration: 1000, duration: 1000,
onComplete: () => globalScene.trainer.setVisible(false) onComplete: () => globalScene.trainer.setVisible(false),
}); });
globalScene.time.delayedCall(750, () => { globalScene.time.delayedCall(750, () => {
playerAnimationPromise = summonPlayerPokemonAnimation(playerPokemon); playerAnimationPromise = summonPlayerPokemonAnimation(playerPokemon);
@ -198,7 +216,13 @@ async function summonPlayerPokemon() {
// Also loads Wobbuffet data (cannot be shiny) // Also loads Wobbuffet data (cannot be shiny)
const enemySpecies = getPokemonSpecies(Species.WOBBUFFET); const enemySpecies = getPokemonSpecies(Species.WOBBUFFET);
globalScene.currentBattle.enemyParty = []; 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.ivs = [0, 0, 0, 0, 0, 0];
wobbuffet.setNature(Nature.MILD); wobbuffet.setNature(Nature.MILD);
wobbuffet.setAlpha(0); wobbuffet.setAlpha(0);
@ -219,6 +243,7 @@ async function summonPlayerPokemon() {
} }
function handleLoseMinigame() { function handleLoseMinigame() {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
return new Promise<void>(async resolve => { return new Promise<void>(async resolve => {
// Check Wobbuffet is still alive // Check Wobbuffet is still alive
const wobbuffet = globalScene.getEnemyPokemon(); const wobbuffet = globalScene.getEnemyPokemon();
@ -258,15 +283,24 @@ function handleNextTurn() {
let isHealPhase = false; let isHealPhase = false;
if (healthRatio < 0.03) { if (healthRatio < 0.03) {
// Grand prize // Grand prize
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.MULTI_LENS ], fillRemaining: false }); setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.MULTI_LENS],
fillRemaining: false,
});
resultMessageKey = `${namespace}:best_result`; resultMessageKey = `${namespace}:best_result`;
} else if (healthRatio < 0.15) { } else if (healthRatio < 0.15) {
// 2nd prize // 2nd prize
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SCOPE_LENS ], fillRemaining: false }); setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.SCOPE_LENS],
fillRemaining: false,
});
resultMessageKey = `${namespace}:great_result`; resultMessageKey = `${namespace}:great_result`;
} else if (healthRatio < 0.33) { } else if (healthRatio < 0.33) {
// 3rd prize // 3rd prize
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.WIDE_LENS ], fillRemaining: false }); setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.WIDE_LENS],
fillRemaining: false,
});
resultMessageKey = `${namespace}:good_result`; resultMessageKey = `${namespace}:good_result`;
} else { } else {
// No prize // No prize
@ -286,14 +320,13 @@ function handleNextTurn() {
// Skip remainder of TurnInitPhase // Skip remainder of TurnInitPhase
return true; return true;
} else { }
if (encounter.misc.turnsRemaining < 3) { if (encounter.misc.turnsRemaining < 3) {
// Display charging messages on turns that aren't the initial turn // Display charging messages on turns that aren't the initial turn
queueEncounterMessage(`${namespace}:charging_continue`); queueEncounterMessage(`${namespace}:charging_continue`);
} }
queueEncounterMessage(`${namespace}:turn_remaining_${encounter.misc.turnsRemaining}`); queueEncounterMessage(`${namespace}:turn_remaining_${encounter.misc.turnsRemaining}`);
encounter.misc.turnsRemaining--; encounter.misc.turnsRemaining--;
}
// Don't skip remainder of TurnInitPhase // Don't skip remainder of TurnInitPhase
return false; return false;
@ -336,7 +369,7 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
globalScene.tweens.add({ globalScene.tweens.add({
targets: pokeball, targets: pokeball,
duration: 650, duration: 650,
x: 100 + fpOffset[0] x: 100 + fpOffset[0],
}); });
globalScene.tweens.add({ globalScene.tweens.add({
@ -387,11 +420,11 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
globalScene.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex())); globalScene.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex()));
resolve(); resolve();
}); });
} },
}); });
} },
}); });
} },
}); });
}); });
} }
@ -408,7 +441,7 @@ function hideShowmanIntroSprite() {
y: "-=16", y: "-=16",
alpha: 0, alpha: 0,
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
duration: 750 duration: 750,
}); });
// Slide the Wobbuffet and Game over slightly // Slide the Wobbuffet and Game over slightly
@ -416,6 +449,6 @@ function hideShowmanIntroSprite() {
targets: [wobbuffet, carnivalGame], targets: [wobbuffet, carnivalGame],
x: "+=16", x: "+=16",
ease: "Sine.easeInOut", 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 {
import { TrainerSlot, } from "#app/data/trainer-config"; 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 { ModifierTier } from "#app/modifier/modifier-tier";
import { MusicPreference } from "#app/system/settings/settings"; import { MusicPreference } from "#app/system/settings/settings";
import type { ModifierTypeOption } from "#app/modifier/modifier-type"; 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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; 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 type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; 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 type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import i18next from "i18next"; import i18next from "i18next";
@ -51,7 +64,7 @@ const LEGENDARY_TRADE_POOLS = {
6: [Species.BUNNELBY, Species.LITLEO, Species.SCATTERBUG], 6: [Species.BUNNELBY, Species.LITLEO, Species.SCATTERBUG],
7: [Species.PIKIPEK, Species.YUNGOOS, Species.ROCKRUFF], 7: [Species.PIKIPEK, Species.YUNGOOS, Species.ROCKRUFF],
8: [Species.SKWOVET, Species.WOOLOO, Species.ROOKIDEE], 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 */ /** Exclude Paradox mons as they aren't considered legendary/mythical */
@ -75,7 +88,7 @@ const EXCLUDED_TRADE_SPECIES = [
Species.IRON_VALIANT, Species.IRON_VALIANT,
Species.IRON_LEAVES, Species.IRON_LEAVES,
Species.IRON_BOULDER, 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3812 | GitHub Issue #3812}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const GlobalTradeSystemEncounter: MysteryEncounter = export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.GLOBAL_TRADE_SYSTEM) MysteryEncounterType.GLOBAL_TRADE_SYSTEM,
)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
@ -96,13 +110,13 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
disableAnimation: true, disableAnimation: true,
x: 3, x: 3,
y: 5, y: 5,
yShadow: 1 yShadow: 1,
} },
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}:intro`, text: `${namespace}:intro`,
} },
]) ])
.setLocalizationKey(`${namespace}`) .setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
@ -128,7 +142,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const tradeOptionsMap: Map<number, EnemyPokemon[]> = getPokemonTradeOptions(); const tradeOptionsMap: Map<number, EnemyPokemon[]> = getPokemonTradeOptions();
encounter.misc = { encounter.misc = {
tradeOptionsMap, tradeOptionsMap,
bgmKey bgmKey,
}; };
return true; return true;
@ -138,8 +152,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
return true; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
@ -168,9 +181,20 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
return true; return true;
}, },
onHover: () => { onHover: () => {
const formName = tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex ? tradePokemon.species.forms[tradePokemon.formIndex].formName : null; const formName =
const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : ""); tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex
const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : ""); ? 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); showEncounterText(`${line1}\n${line2}`, 0, 0, false);
}, },
}; };
@ -184,7 +208,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon; const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon; 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 // Generate a trainer name
const traderName = generateRandomTraderName(); const traderName = generateRandomTraderName();
@ -198,7 +224,18 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
// Pokeball to Ultra ball, randomly // Pokeball to Ultra ball, randomly
receivedPokemonData.pokeball = randInt(4) as PokeballType; receivedPokemonData.pokeball = randInt(4) as PokeballType;
const dataSource = new PokemonData(receivedPokemonData); 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); globalScene.getPlayerParty().push(newPlayerPokemon);
await newPlayerPokemon.loadAssets(); await newPlayerPokemon.loadAssets();
@ -218,11 +255,10 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
@ -293,7 +329,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon; const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon;
const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon; 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 // Generate a trainer name
const traderName = generateRandomTraderName(); const traderName = generateRandomTraderName();
@ -306,7 +344,18 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
receivedPokemonData.passive = tradedPokemon.passive; receivedPokemonData.passive = tradedPokemon.passive;
receivedPokemonData.pokeball = randInt(4) as PokeballType; receivedPokemonData.pokeball = randInt(4) as PokeballType;
const dataSource = new PokemonData(receivedPokemonData); 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); globalScene.getPlayerParty().push(newPlayerPokemon);
await newPlayerPokemon.loadAssets(); await newPlayerPokemon.loadAssets();
@ -326,11 +375,10 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
@ -340,7 +388,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => { const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones // Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => { const validItems = pokemon.getHeldItems().filter(it => {
return it.isTransferable; return it.isTransferable;
}); });
@ -361,7 +409,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
// If pokemon has items to trade // If pokemon has items to trade
const meetsReqs = pokemon.getHeldItems().filter((it) => { const meetsReqs =
pokemon.getHeldItems().filter(it => {
return it.isTransferable; return it.isTransferable;
}).length > 0; }).length > 0;
if (!meetsReqs) { if (!meetsReqs) {
@ -399,11 +448,17 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
let item: ModifierTypeOption | null = null; let item: ModifierTypeOption | null = null;
// TMs excluded from possible rewards // TMs excluded from possible rewards
while (!item || item.type.id.includes("TM_")) { 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); encounter.setDialogueToken("itemName", item.type.name);
setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false }); setEncounterRewards({
guaranteedModifierTypeOptions: [item],
fillRemaining: false,
});
chosenPokemon.loseHeldItem(modifier, false); chosenPokemon.loseHeldItem(modifier, false);
await globalScene.updateModifiers(true, true); await globalScene.updateModifiers(true, true);
@ -414,7 +469,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
await showEncounterText(`${namespace}:item_trade_selected`); await showEncounterText(`${namespace}:item_trade_selected`);
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -430,7 +485,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp // Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .build();
@ -439,7 +494,7 @@ function getPokemonTradeOptions(): Map<number, EnemyPokemon[]> {
// Starts by filtering out any current party members as valid resulting species // Starts by filtering out any current party members as valid resulting species
const alreadyUsedSpecies: PokemonSpecies[] = globalScene.getPlayerParty().map(p => p.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 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) { if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) {
const generation = pokemon.species.generation; const generation = pokemon.species.generation;
@ -459,11 +514,14 @@ function getPokemonTradeOptions(): Map<number, EnemyPokemon[]> {
} }
// Add trade options to map // 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 new EnemyPokemon(s, pokemon.level, TrainerSlot.NONE, false);
})); }),
);
}
} }
});
return tradeOptionsMap; return tradeOptionsMap;
} }
@ -478,8 +536,7 @@ function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?:
} }
while (isNullOrUndefined(newSpecies)) { while (isNullOrUndefined(newSpecies)) {
// Get all non-legendary species that fall within the Bst range requirements // Get all non-legendary species that fall within the Bst range requirements
let validSpecies = allSpecies let validSpecies = allSpecies.filter(s => {
.filter(s => {
const isLegendaryOrMythical = s.legendary || s.subLegendary || s.mythical; const isLegendaryOrMythical = s.legendary || s.subLegendary || s.mythical;
const speciesBst = s.getBaseStatTotal(); const speciesBst = s.getBaseStatTotal();
const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap; const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap;
@ -508,7 +565,13 @@ function showTradeBackground() {
const tradeContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); const tradeContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6);
tradeContainer.setName("Trade Background"); 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.setName("Black Background");
flyByStaticBg.setOrigin(0, 0); flyByStaticBg.setOrigin(0, 0);
flyByStaticBg.setVisible(false); flyByStaticBg.setVisible(false);
@ -531,7 +594,7 @@ function showTradeBackground() {
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
onComplete: () => { onComplete: () => {
resolve(); resolve();
} },
}); });
}); });
} }
@ -548,7 +611,7 @@ function hideTradeBackground() {
onComplete: () => { onComplete: () => {
globalScene.fieldUI.remove(transformationContainer, true); globalScene.fieldUI.remove(transformationContainer, true);
resolve(); resolve();
} },
}); });
}); });
} }
@ -569,8 +632,16 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
let receivedPokemonTintSprite: Phaser.GameObjects.Sprite; let receivedPokemonTintSprite: Phaser.GameObjects.Sprite;
const getPokemonSprite = () => { const getPokemonSprite = () => {
const ret = globalScene.addPokemonSprite(tradedPokemon, tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pkmn__sub"); const ret = globalScene.addPokemonSprite(
ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); 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; return ret;
}; };
@ -594,7 +665,12 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
console.error(`Failed to play animation for ${spriteKey}`, err); 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("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey());
sprite.setPipelineData("shiny", tradedPokemon.shiny); sprite.setPipelineData("shiny", tradedPokemon.shiny);
@ -615,7 +691,12 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
console.error(`Failed to play animation for ${spriteKey}`, err); 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("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey());
sprite.setPipelineData("shiny", receivedPokemon.shiny); sprite.setPipelineData("shiny", receivedPokemon.shiny);
@ -630,13 +711,23 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
// Traded pokemon pokeball // Traded pokemon pokeball
const tradedPbAtlasKey = getPokeballAtlasKey(tradedPokemon.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); tradedPokeball.setVisible(false);
tradeContainer.add(tradedPokeball); tradeContainer.add(tradedPokeball);
// Received pokemon pokeball // Received pokemon pokeball
const receivedPbAtlasKey = getPokeballAtlasKey(receivedPokemon.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); receivedPokeball.setVisible(false);
tradeContainer.add(receivedPokeball); tradeContainer.add(receivedPokeball);
@ -700,22 +791,31 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
}, },
onComplete: async () => { onComplete: async () => {
await doPokemonTradeFlyBySequence(tradedPokemonSprite, receivedPokemonSprite); await doPokemonTradeFlyBySequence(tradedPokemonSprite, receivedPokemonSprite);
await doTradeReceivedSequence(receivedPokemon, receivedPokemonSprite, receivedPokemonTintSprite, receivedPokeball, receivedPbAtlasKey); await doTradeReceivedSequence(
receivedPokemon,
receivedPokemonSprite,
receivedPokemonTintSprite,
receivedPokeball,
receivedPbAtlasKey,
);
resolve(); 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 => { return new Promise<void>(resolve => {
const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container; const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image; 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.y = 200;
tradedPokemonSprite.scale = 1; tradedPokemonSprite.scale = 1;
tradedPokemonSprite.setVisible(true); tradedPokemonSprite.setVisible(true);
receivedPokemonSprite.x = tradeBaseBg.displayWidth * 3 / 4; receivedPokemonSprite.x = (tradeBaseBg.displayWidth * 3) / 4;
receivedPokemonSprite.y = -200; receivedPokemonSprite.y = -200;
receivedPokemonSprite.scale = 1; receivedPokemonSprite.scale = 1;
receivedPokemonSprite.setVisible(true); receivedPokemonSprite.setVisible(true);
@ -755,11 +855,11 @@ function doPokemonTradeFlyBySequence(tradedPokemonSprite: Phaser.GameObjects.Spr
x: tradeBaseBg.displayWidth / 4, x: tradeBaseBg.displayWidth / 4,
ease: "Cubic.easeInOut", ease: "Cubic.easeInOut",
duration: BASE_ANIM_DURATION / 2, duration: BASE_ANIM_DURATION / 2,
delay: ANIM_DELAY delay: ANIM_DELAY,
}); });
globalScene.tweens.add({ globalScene.tweens.add({
targets: tradedPokemonSprite, targets: tradedPokemonSprite,
x: tradeBaseBg.displayWidth * 3 / 4, x: (tradeBaseBg.displayWidth * 3) / 4,
ease: "Cubic.easeInOut", ease: "Cubic.easeInOut",
duration: BASE_ANIM_DURATION / 2, duration: BASE_ANIM_DURATION / 2,
delay: ANIM_DELAY, delay: ANIM_DELAY,
@ -785,20 +885,26 @@ function doPokemonTradeFlyBySequence(tradedPokemonSprite: Phaser.GameObjects.Spr
duration: FADE_DELAY, duration: FADE_DELAY,
onComplete: () => { onComplete: () => {
resolve(); 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 => { return new Promise<void>(resolve => {
const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container; const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container;
const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image; const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image;
@ -851,7 +957,7 @@ function doTradeReceivedSequence(receivedPokemon: PlayerPokemon, receivedPokemon
targets: receivedPokemonSprite, targets: receivedPokemonSprite,
duration: 250, duration: 250,
ease: "Sine.easeOut", ease: "Sine.easeOut",
scale: 1 scale: 1,
}); });
globalScene.tweens.add({ globalScene.tweens.add({
targets: receivedPokemonTintSprite, targets: receivedPokemonTintSprite,
@ -867,10 +973,10 @@ function doTradeReceivedSequence(receivedPokemon: PlayerPokemon, receivedPokemon
} }
receivedPokeballSprite.destroy(); receivedPokeballSprite.destroy();
globalScene.time.delayedCall(2000, () => resolve()); globalScene.time.delayedCall(2000, () => resolve());
} },
}); });
}); });
} },
}); });
}); });
} }
@ -884,7 +990,7 @@ function generateRandomTraderName() {
} }
// Some trainers have 2 gendered pools, some do not // Some trainers have 2 gendered pools, some do not
const genderedPool = trainerTypePool[randInt(trainerTypePool.length)]; 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 // Some names have an '&' symbol and need to be trimmed to a single name instead of a double name
const trainerNames = trainerNameString.split(" & "); const trainerNames = trainerNameString.split(" & ");
return trainerNames[randInt(trainerNames.length)]; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3793 | GitHub Issue #3793}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @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) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([ .withIntroSpriteConfigs([
@ -57,8 +59,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
// Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/ // Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE) .withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
@ -72,12 +73,11 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
], ],
}) })
.withOptionPhase(async () => handlePokemonGuidingYouPhase()) .withOptionPhase(async () => handlePokemonGuidingYouPhase())
.build() .build(),
) )
.withOption( .withOption(
//Option 2: Use a (non fainted) pokemon that can learn fly to guide you back. //Option 2: Use a (non fainted) pokemon that can learn fly to guide you back.
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE) .withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
@ -91,7 +91,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
], ],
}) })
.withOptionPhase(async () => handlePokemonGuidingYouPhase()) .withOptionPhase(async () => handlePokemonGuidingYouPhase())
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
// Option 3: Wander aimlessly // Option 3: Wander aimlessly
@ -105,7 +105,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
], ],
}, },
async () => { async () => {
const allowedPokemon = globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle()); const allowedPokemon = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle());
for (const pkm of allowedPokemon) { for (const pkm of allowedPokemon) {
const percentage = DAMAGE_PERCENTAGE / 100; const percentage = DAMAGE_PERCENTAGE / 100;
@ -116,7 +116,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
return true; return true;
} },
) )
.withOutroDialogue([ .withOutroDialogue([
{ {

View File

@ -1,5 +1,4 @@
import type { import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { import {
initBattleWithEnemyConfig, initBattleWithEnemyConfig,
setEncounterRewards, setEncounterRewards,
@ -29,8 +28,9 @@ const namespace = "mysteryEncounters/mysteriousChallengers";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3801 | GitHub Issue #3801} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3801 | GitHub Issue #3801}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const MysteriousChallengersEncounter: MysteryEncounter = export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS) MysteryEncounterType.MYSTERIOUS_CHALLENGERS,
)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // These are set in onInit() .withIntroSpriteConfigs([]) // These are set in onInit()
@ -71,8 +71,8 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 20), 5), Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 20), 5),
PartyMemberStrength.AVERAGE, PartyMemberStrength.AVERAGE,
false, false,
true true,
) ),
); );
const hardConfig = trainerConfigs[hardTrainerType].clone(); const hardConfig = trainerConfigs[hardTrainerType].clone();
hardConfig.setPartyTemplates(hardTemplate); 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 // 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 // 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( const brutalTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex, true);
globalScene.currentBattle.waveIndex,
true
);
const e4Template = trainerPartyTemplates.ELITE_FOUR; const e4Template = trainerPartyTemplates.ELITE_FOUR;
const brutalConfig = trainerConfigs[brutalTrainerType].clone(); const brutalConfig = trainerConfigs[brutalTrainerType].clone();
brutalConfig.title = trainerConfigs[brutalTrainerType].title; brutalConfig.title = trainerConfigs[brutalTrainerType].title;
@ -152,7 +149,10 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
// Spawn standard trainer battle with memory mushroom reward // Spawn standard trainer battle with memory mushroom reward
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; 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 // Seed offsets to remove possibility of different trainers having exact same teams
let initBattlePromise: Promise<void>; let initBattlePromise: Promise<void>;
@ -160,7 +160,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
initBattlePromise = initBattleWithEnemyConfig(config); initBattlePromise = initBattleWithEnemyConfig(config);
}, globalScene.currentBattle.waveIndex * 10); }, globalScene.currentBattle.waveIndex * 10);
await initBattlePromise!; await initBattlePromise!;
} },
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -177,7 +177,10 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
// Spawn hard fight // Spawn hard fight
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1]; 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 // Seed offsets to remove possibility of different trainers having exact same teams
let initBattlePromise: Promise<void>; let initBattlePromise: Promise<void>;
@ -185,7 +188,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
initBattlePromise = initBattleWithEnemyConfig(config); initBattlePromise = initBattleWithEnemyConfig(config);
}, globalScene.currentBattle.waveIndex * 100); }, globalScene.currentBattle.waveIndex * 100);
await initBattlePromise!; await initBattlePromise!;
} },
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -205,7 +208,10 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
// To avoid player level snowballing from picking this option // To avoid player level snowballing from picking this option
encounter.expMultiplier = 0.9; 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 // Seed offsets to remove possibility of different trainers having exact same teams
let initBattlePromise: Promise<void>; let initBattlePromise: Promise<void>;
@ -213,7 +219,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
initBattlePromise = initBattleWithEnemyConfig(config); initBattlePromise = initBattleWithEnemyConfig(config);
}, globalScene.currentBattle.waveIndex * 1000); }, globalScene.currentBattle.waveIndex * 1000);
await initBattlePromise!; await initBattlePromise!;
} },
) )
.withOutroDialogue([ .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 { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; 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 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 {
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; 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 { getPokemonSpecies } from "#app/data/pokemon-species";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { ModifierTier } from "#app/modifier/modifier-tier"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3796 | GitHub Issue #3796}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const MysteriousChestEncounter: MysteryEncounter = export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST) MysteryEncounterType.MYSTERIOUS_CHEST,
)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(2, 6, true) .withScenePartySizeRequirement(2, 6, true)
@ -57,12 +66,12 @@ export const MysteriousChestEncounter: MysteryEncounter =
yShadow: 6, yShadow: 6,
alpha: 0, alpha: 0,
disableAnimation: true, // Re-enabled after option select disableAnimation: true, // Re-enabled after option select
} },
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}:intro`, text: `${namespace}:intro`,
} },
]) ])
.setLocalizationKey(`${namespace}`) .setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
@ -80,8 +89,8 @@ export const MysteriousChestEncounter: MysteryEncounter =
species: getPokemonSpecies(Species.GIMMIGHOUL), species: getPokemonSpecies(Species.GIMMIGHOUL),
formIndex: 0, formIndex: 0,
isBoss: true, 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; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
@ -116,7 +124,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
// Determine roll first // Determine roll first
const roll = randSeedInt(RAND_LENGTH); const roll = randSeedInt(RAND_LENGTH);
encounter.misc = { encounter.misc = {
roll roll,
}; };
if (roll < TRAP_PERCENT) { if (roll < TRAP_PERCENT) {
@ -137,12 +145,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) { if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) {
// Choose between 2 COMMON / 2 GREAT tier items (20%) // Choose between 2 COMMON / 2 GREAT tier items (20%)
setEncounterRewards({ setEncounterRewards({
guaranteedModifierTiers: [ guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.GREAT],
ModifierTier.COMMON,
ModifierTier.COMMON,
ModifierTier.GREAT,
ModifierTier.GREAT,
],
}); });
// Display result message then proceed to rewards // Display result message then proceed to rewards
queueEncounterMessage(`${namespace}:option.1.normal`); queueEncounterMessage(`${namespace}:option.1.normal`);
@ -150,24 +153,27 @@ export const MysteriousChestEncounter: MysteryEncounter =
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) { } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) {
// Choose between 3 ULTRA tier items (30%) // Choose between 3 ULTRA tier items (30%)
setEncounterRewards({ setEncounterRewards({
guaranteedModifierTiers: [ guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA],
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
}); });
// Display result message then proceed to rewards // Display result message then proceed to rewards
queueEncounterMessage(`${namespace}:option.1.good`); queueEncounterMessage(`${namespace}:option.1.good`);
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) { } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) {
// Choose between 2 ROGUE tier items (10%) // 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 // Display result message then proceed to rewards
queueEncounterMessage(`${namespace}:option.1.great`); queueEncounterMessage(`${namespace}:option.1.great`);
leaveEncounterWithoutBattle(); 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%) // Choose 1 MASTER tier item (5%)
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.MASTER ]}); setEncounterRewards({
guaranteedModifierTiers: [ModifierTier.MASTER],
});
// Display result message then proceed to rewards // Display result message then proceed to rewards
queueEncounterMessage(`${namespace}:option.1.amazing`); queueEncounterMessage(`${namespace}:option.1.amazing`);
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
@ -193,7 +199,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
} }
} }
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -209,6 +215,6 @@ export const MysteriousChestEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp // Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .build();

View File

@ -1,5 +1,12 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; 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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3813 | GitHub Issue #3813}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const PartTimerEncounter: MysteryEncounter = export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.PART_TIMER) MysteryEncounterType.PART_TIMER,
)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([ .withIntroSpriteConfigs([
@ -34,15 +42,15 @@ export const PartTimerEncounter: MysteryEncounter =
fileRoot: "mystery-encounters", fileRoot: "mystery-encounters",
hasShadow: false, hasShadow: false,
y: 6, y: 6,
x: 15 x: 15,
}, },
{ {
spriteKey: "worker_f", spriteKey: "worker_f",
fileRoot: "trainer", fileRoot: "trainer",
hasShadow: true, hasShadow: true,
x: -18, x: -18,
y: 4 y: 4,
} },
]) ])
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.withIntroDialogue([ .withIntroDialogue([
@ -75,16 +83,16 @@ export const PartTimerEncounter: MysteryEncounter =
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withOption(MysteryEncounterOptionBuilder .withOption(
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.1.selected` text: `${namespace}:option.1.selected`,
} },
] ],
}) })
.withPreOptionPhase(async () => { .withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!; 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 // 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. // Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
// Calculation from Pokemon.calculateStats // 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 percentDiff = (pokemon.getStat(Stat.SPD) - baselineValue) / baselineValue;
const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4); const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4);
encounter.misc = { encounter.misc = {
moneyMultiplier moneyMultiplier,
}; };
// Reduce all PP to 2 (if they started at greater than 2) // Reduce all PP to 2 (if they started at greater than 2)
pokemon.moveset.forEach(move => { for (const move of pokemon.moveset) {
if (move) { if (move) {
const newPpUsed = move.getMovePp() - 2; const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed; move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
} }
}); }
setEncounterExp(pokemon.id, 100); setEncounterExp(pokemon.id, 100);
@ -141,24 +149,28 @@ export const PartTimerEncounter: MysteryEncounter =
} }
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier); const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(moneyChange, true, false); 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`); await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards({ fillRemaining: true }); setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}) })
.build() .build(),
) )
.withOption(MysteryEncounterOptionBuilder .withOption(
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.2.selected` text: `${namespace}:option.2.selected`,
} },
] ],
}) })
.withPreOptionPhase(async () => { .withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!; 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 // 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. // Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
// Calculation from Pokemon.calculateStats // Calculation from Pokemon.calculateStats
const baselineHp = Math.floor(((2 * 75 + 16) * pokemon.level) * 0.01) + pokemon.level + 10; 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 baselineAtkDef = Math.floor((2 * 75 + 16) * pokemon.level * 0.01) + 5;
const baselineValue = baselineHp + 1.5 * (baselineAtkDef * 2); 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 percentDiff = (strongestValue - baselineValue) / baselineValue;
const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4); const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4);
encounter.misc = { encounter.misc = {
moneyMultiplier moneyMultiplier,
}; };
// Reduce all PP to 2 (if they started at greater than 2) // Reduce all PP to 2 (if they started at greater than 2)
pokemon.moveset.forEach(move => { for (const move of pokemon.moveset) {
if (move) { if (move) {
const newPpUsed = move.getMovePp() - 2; const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed; move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
} }
}); }
setEncounterExp(pokemon.id, 100); setEncounterExp(pokemon.id, 100);
@ -218,17 +231,20 @@ export const PartTimerEncounter: MysteryEncounter =
} }
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier); const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(moneyChange, true, false); 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`); await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards({ fillRemaining: true }); setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
@ -246,12 +262,12 @@ export const PartTimerEncounter: MysteryEncounter =
encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender()); encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender());
// Reduce all PP to 2 (if they started at greater than 2) // Reduce all PP to 2 (if they started at greater than 2)
selectedPokemon.moveset.forEach(move => { for (const move of selectedPokemon.moveset) {
if (move) { if (move) {
const newPpUsed = move.getMovePp() - 2; const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed; move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
} }
}); }
setEncounterExp(selectedPokemon.id, 100); setEncounterExp(selectedPokemon.id, 100);
@ -270,19 +286,23 @@ export const PartTimerEncounter: MysteryEncounter =
await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`); await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`);
const moneyChange = globalScene.getWaveMoneyAmount(2.5); const moneyChange = globalScene.getWaveMoneyAmount(2.5);
updatePlayerMoney(moneyChange, true, false); 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`); await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards({ fillRemaining: true }); setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}) })
.build() .build(),
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}:outro`, text: `${namespace}:outro`,
} },
]) ])
.build(); .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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; 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 type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; 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 { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3800 | GitHub Issue #3800}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const SafariZoneEncounter: MysteryEncounter = export const SafariZoneEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE) MysteryEncounterType.SAFARI_ZONE,
)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive .withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
@ -50,7 +61,7 @@ export const SafariZoneEncounter: MysteryEncounter =
fileRoot: "mystery-encounters", fileRoot: "mystery-encounters",
hasShadow: false, hasShadow: false,
x: 4, x: 4,
y: 6 y: 6,
}, },
]) ])
.withIntroDialogue([ .withIntroDialogue([
@ -66,8 +77,8 @@ export const SafariZoneEncounter: MysteryEncounter =
globalScene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString()); globalScene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString());
return true; return true;
}) })
.withOption(MysteryEncounterOptionBuilder .withOption(
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive .withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
@ -83,7 +94,7 @@ export const SafariZoneEncounter: MysteryEncounter =
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.continuousEncounter = true; encounter.continuousEncounter = true;
encounter.misc = { encounter.misc = {
safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS,
}; };
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
// Load bait/mud assets // Load bait/mud assets
@ -96,10 +107,13 @@ export const SafariZoneEncounter: MysteryEncounter =
globalScene.currentBattle.enemyParty = []; globalScene.currentBattle.enemyParty = [];
await transitionMysteryEncounterIntroVisuals(); await transitionMysteryEncounterIntroVisuals();
await summonSafariPokemon(); await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, hideDescription: true }); initSubsequentOptionSelect({
overrideOptions: safariZoneGameOptions,
hideDescription: true,
});
return true; return true;
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -115,7 +129,7 @@ export const SafariZoneEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp // Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .build();
@ -135,15 +149,14 @@ export const SafariZoneEncounter: MysteryEncounter =
* Flee chance = fleeRate / 255 * Flee chance = fleeRate / 255
*/ */
const safariZoneGameOptions: MysteryEncounterOption[] = [ const safariZoneGameOptions: MysteryEncounterOption[] = [
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:safari.1.label`, buttonLabel: `${namespace}:safari.1.label`,
buttonTooltip: `${namespace}:safari.1.tooltip`, buttonTooltip: `${namespace}:safari.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:safari.1.selected`, text: `${namespace}:safari.1.selected`,
} },
], ],
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
@ -157,7 +170,11 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
// Check how many safari pokemon left // Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) { if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(); await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true }); initSubsequentOptionSelect({
overrideOptions: safariZoneGameOptions,
startingCursorIndex: 0,
hideDescription: true,
});
} else { } else {
// End safari mode // End safari mode
encounter.continuousEncounter = false; encounter.continuousEncounter = false;
@ -170,8 +187,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
return true; return true;
}) })
.build(), .build(),
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:safari.2.label`, buttonLabel: `${namespace}:safari.2.label`,
buttonTooltip: `${namespace}:safari.2.tooltip`, buttonTooltip: `${namespace}:safari.2.tooltip`,
@ -200,8 +216,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
return true; return true;
}) })
.build(), .build(),
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:safari.3.label`, buttonLabel: `${namespace}:safari.3.label`,
buttonTooltip: `${namespace}:safari.3.tooltip`, buttonTooltip: `${namespace}:safari.3.tooltip`,
@ -229,8 +244,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
return true; return true;
}) })
.build(), .build(),
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:safari.4.label`, buttonLabel: `${namespace}:safari.4.label`,
buttonTooltip: `${namespace}:safari.4.tooltip`, buttonTooltip: `${namespace}:safari.4.tooltip`,
@ -243,7 +257,11 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
// Check how many safari pokemon left // Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) { if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(); await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true }); initSubsequentOptionSelect({
overrideOptions: safariZoneGameOptions,
startingCursorIndex: 3,
hideDescription: true,
});
} else { } else {
// End safari mode // End safari mode
encounter.continuousEncounter = false; encounter.continuousEncounter = false;
@ -251,7 +269,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
} }
return true; return true;
}) })
.build() .build(),
]; ];
async function summonSafariPokemon() { 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 // 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 // Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
let enemySpecies; let enemySpecies: PokemonSpecies;
let pokemon; let pokemon: any;
globalScene.executeWithSeedOffset(() => { globalScene.executeWithSeedOffset(
() => {
enemySpecies = getSafariSpeciesSpawn(); enemySpecies = getSafariSpeciesSpawn();
const level = globalScene.currentBattle.getLevelForWave(); const level = globalScene.currentBattle.getLevelForWave();
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, globalScene.gameMode)); enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, globalScene.gameMode));
@ -293,7 +312,9 @@ async function summonSafariPokemon() {
pokemon.calculateStats(); pokemon.calculateStats();
globalScene.currentBattle.enemyParty.unshift(pokemon); globalScene.currentBattle.enemyParty.unshift(pokemon);
}, globalScene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining); },
globalScene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining,
);
globalScene.gameData.setPokemonSeen(pokemon, true); globalScene.gameData.setPokemonSeen(pokemon, true);
await pokemon.loadAssets(); await pokemon.loadAssets();
@ -324,7 +345,8 @@ function throwPokeball(pokemon: EnemyPokemon): Promise<boolean> {
// Catch stage ranges from -6 to +6 (like stat boost stages) // Catch stage ranges from -6 to +6 (like stat boost stages)
const safariCatchStage = globalScene.currentBattle.mysteryEncounter!.misc.catchStage; const safariCatchStage = globalScene.currentBattle.mysteryEncounter!.misc.catchStage;
// Catch modifier ranges from 2/8 (-6 stage) to 8/2 (+6) // 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 // Catch rate same as safari ball
const pokeballMultiplier = 1.5; const pokeballMultiplier = 1.5;
const catchRate = Math.round(baseCatchRate * pokeballMultiplier * safariModifier); const catchRate = Math.round(baseCatchRate * pokeballMultiplier * safariModifier);
@ -341,7 +363,9 @@ async function throwBait(pokemon: EnemyPokemon): Promise<boolean> {
globalScene.field.add(bait); globalScene.field.add(bait);
return new Promise(resolve => { 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.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
globalScene.playSound("se/pb_throw"); 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.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
globalScene.trainer.setFrame("3"); globalScene.trainer.setFrame("3");
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => { 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" }, y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
duration: 500, duration: 500,
onComplete: () => { onComplete: () => {
let index = 1; let index = 1;
globalScene.time.delayedCall(768, () => { globalScene.time.delayedCall(768, () => {
globalScene.tweens.add({ globalScene.tweens.add({
@ -389,10 +414,10 @@ async function throwBait(pokemon: EnemyPokemon): Promise<boolean> {
bait.destroy(); bait.destroy();
resolve(true); resolve(true);
}); });
} },
}); });
}); });
} },
}); });
}); });
}); });
@ -407,7 +432,9 @@ async function throwMud(pokemon: EnemyPokemon): Promise<boolean> {
globalScene.field.add(mud); globalScene.field.add(mud);
return new Promise(resolve => { 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.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
globalScene.playSound("se/pb_throw"); 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.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
globalScene.trainer.setFrame("3"); globalScene.trainer.setFrame("3");
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => { 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: () => { onComplete: () => {
resolve(true); resolve(true);
} },
}); });
} },
}); });
} },
}); });
}); });
}); });
@ -474,7 +503,7 @@ async function throwMud(pokemon: EnemyPokemon): Promise<boolean> {
function isPokemonFlee(pokemon: EnemyPokemon, fleeStage: number): boolean { function isPokemonFlee(pokemon: EnemyPokemon, fleeStage: number): boolean {
const speciesCatchRate = pokemon.species.catchRate; const speciesCatchRate = pokemon.species.catchRate;
const fleeModifier = (2 + Math.min(Math.max(fleeStage, 0), 6)) / (2 - Math.max(Math.min(fleeStage, 0), -6)); 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); console.log("Flee rate: " + fleeRate);
const roll = randSeedInt(256); const roll = randSeedInt(256);
console.log("Roll: " + roll); console.log("Roll: " + roll);
@ -519,7 +548,11 @@ async function doEndTurn(cursorIndex: number) {
// Check how many safari pokemon left // Check how many safari pokemon left
if (encounter.misc.safariPokemonRemaining > 0) { if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(); await summonSafariPokemon();
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true }); initSubsequentOptionSelect({
overrideOptions: safariZoneGameOptions,
startingCursorIndex: cursorIndex,
hideDescription: true,
});
} else { } else {
// End safari mode // End safari mode
encounter.continuousEncounter = false; encounter.continuousEncounter = false;
@ -527,7 +560,11 @@ async function doEndTurn(cursorIndex: number) {
} }
} else { } else {
globalScene.queueMessage(getEncounterText(`${namespace}:safari.watching`) ?? "", 0, null, 1000); 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. * @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
*/ */
export function getSafariSpeciesSpawn(): PokemonSpecies { 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 { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type"; 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 { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; 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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type { Nature } from "#enums/nature"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3798 | GitHub Issue #3798}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ShadyVitaminDealerEncounter: MysteryEncounter = export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER) MysteryEncounterType.SHADY_VITAMIN_DEALER,
)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .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 .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, repeat: true,
x: 12, x: 12,
y: -5, y: -5,
yShadow: -5 yShadow: -5,
}, },
{ {
spriteKey: "shady_vitamin_dealer", spriteKey: "shady_vitamin_dealer",
@ -52,7 +63,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
hasShadow: true, hasShadow: true,
x: -12, x: -12,
y: 3, y: 3,
yShadow: 3 yShadow: 3,
}, },
]) ])
.withIntroDialogue([ .withIntroDialogue([
@ -69,8 +80,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER) .withSceneMoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
@ -103,7 +113,11 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
// If pokemon meets primary pokemon reqs, it can be selected // If pokemon meets primary pokemon reqs, it can be selected
if (!pokemon.isAllowedInChallenge()) { 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)) { if (!encounter.pokemonMeetsPrimaryRequirements(pokemon)) {
return getEncounterText(`${namespace}:invalid_selection`) ?? null; return getEncounterText(`${namespace}:invalid_selection`) ?? null;
@ -146,11 +160,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
setEncounterExp([chosenPokemon.id], 100); setEncounterExp([chosenPokemon.id], 100);
await chosenPokemon.updateInfo(); await chosenPokemon.updateInfo();
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER) .withSceneMoneyRequirement(0, VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
@ -208,7 +221,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
await chosenPokemon.updateInfo(); await chosenPokemon.updateInfo();
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -217,14 +230,14 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
selected: [ selected: [
{ {
text: `${namespace}:option.3.selected`, text: `${namespace}:option.3.selected`,
speaker: `${namespace}:speaker` speaker: `${namespace}:speaker`,
} },
] ],
}, },
async () => { async () => {
// Leave encounter with no rewards or exp // Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .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 { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils"; 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 { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3815 | GitHub Issue #3815}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const SlumberingSnorlaxEncounter: MysteryEncounter = export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX) MysteryEncounterType.SLUMBERING_SNORLAX,
)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withCatchAllowed(true) .withCatchAllowed(true)
@ -69,15 +77,15 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
modifierConfigs: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
stackCount: 2 stackCount: 2,
}, },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
stackCount: 2 stackCount: 2,
}, },
], ],
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), 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 = { const config: EnemyPartyConfig = {
levelAdditiveModifier: 0.5, levelAdditiveModifier: 0.5,
@ -109,22 +117,26 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
async () => { async () => {
// Pick battle // Pick battle
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: true }); setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
fillRemaining: true,
});
encounter.startOfBattleEffects.push( encounter.startOfBattleEffects.push(
{ {
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.SNORE), move: new PokemonMove(Moves.SNORE),
ignorePp: true ignorePp: true,
}, },
{ {
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.SNORE), move: new PokemonMove(Moves.SNORE),
ignorePp: true ignorePp: true,
}); },
);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
} },
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -142,11 +154,10 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
globalScene.unshiftPhase(new PartyHealPhase(true)); globalScene.unshiftPhase(new PartyHealPhase(true));
queueEncounterMessage(`${namespace}:option.2.rest_result`); queueEncounterMessage(`${namespace}:option.2.rest_result`);
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
} },
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true))
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
@ -154,18 +165,21 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`, disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.3.selected` text: `${namespace}:option.3.selected`,
} },
] ],
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
// Steal the Snorlax's Leftovers // Steal the Snorlax's Leftovers
const instance = globalScene.currentBattle.mysteryEncounter!; 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 // Snorlax exp to Pokemon that did the stealing
setEncounterExp(instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp); setEncounterExp(instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp);
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}) })
.build() .build(),
) )
.build(); .build();

View File

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

View File

@ -1,5 +1,9 @@
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 { 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 { trainerConfigs } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -51,28 +55,64 @@ class BreederSpeciesEvolution {
const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [ const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
[Species.MUNCHLAX, new BreederSpeciesEvolution(Species.SNORLAX, SECOND_STAGE_EVOLUTION_WAVE)], [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.HAPPINY,
[ Species.ELEKID, new BreederSpeciesEvolution(Species.ELECTABUZZ, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ELECTIVIRE, FINAL_STAGE_EVOLUTION_WAVE) ], 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.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.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)[][] = [ 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.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, 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.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, 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.WYNAUT, new BreederSpeciesEvolution(Species.WOBBUFFET, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.CHINGLING, new BreederSpeciesEvolution(Species.CHIMECHO, 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.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 {@link https://github.com/pagefaultgames/pokerogue/issues/3818 | GitHub Issue #3818}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const TheExpertPokemonBreederEncounter: MysteryEncounter = export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER) MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER,
)
.withEncounterTier(MysteryEncounterTier.ULTRA) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(4, 6, true) // Must have at least 4 legal pokemon in party .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 // Calculates what trainers are available for battle in the encounter
// If player is in space biome, uses special "Space" version of the trainer // If player is in space biome, uses special "Space" version of the trainer
encounter.enemyPartyConfigs = [ encounter.enemyPartyConfigs = [getPartyConfig()];
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 = [ encounter.spriteConfigs = [
{ {
spriteKey: cleffaSpecies.toString(), spriteKey: cleffaSpecies.toString(),
@ -114,7 +158,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
repeat: true, repeat: true,
x: 14, x: 14,
y: -2, y: -2,
yShadow: -2 yShadow: -2,
}, },
{ {
spriteKey: "expert_pokemon_breeder", spriteKey: "expert_pokemon_breeder",
@ -122,15 +166,13 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
hasShadow: true, hasShadow: true,
x: -14, x: -14,
y: 4, y: 4,
yShadow: 2 yShadow: 2,
}, },
]; ];
// Determine the 3 pokemon the player can battle with // Determine the 3 pokemon the player can battle with
let partyCopy = globalScene.getPlayerParty().slice(0); let partyCopy = globalScene.getPlayerParty().slice(0);
partyCopy = partyCopy partyCopy = partyCopy.filter(p => p.isAllowedInBattle()).sort((a, b) => a.friendship - b.friendship);
.filter(p => p.isAllowedInBattle())
.sort((a, b) => a.friendship - b.friendship);
const pokemon1 = partyCopy[0]; const pokemon1 = partyCopy[0];
const pokemon2 = partyCopy[1]; const pokemon2 = partyCopy[1];
@ -143,13 +185,23 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
const [pokemon1CommonEggs, pokemon1RareEggs] = calculateEggRewardsForPokemon(pokemon1); const [pokemon1CommonEggs, pokemon1RareEggs] = calculateEggRewardsForPokemon(pokemon1);
let pokemon1Tooltip = getEncounterText(`${namespace}:option.1.tooltip_base`)!; let pokemon1Tooltip = getEncounterText(`${namespace}:option.1.tooltip_base`)!;
if (pokemon1RareEggs > 0) { if (pokemon1RareEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1RareEggs, rarity: i18next.t("egg:greatTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, {
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); count: pokemon1RareEggs,
rarity: i18next.t("egg:greatTier"),
});
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon1RareEggs", eggsText); encounter.setDialogueToken("pokemon1RareEggs", eggsText);
} }
if (pokemon1CommonEggs > 0) { if (pokemon1CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1CommonEggs, rarity: i18next.t("egg:defaultTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, {
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); count: pokemon1CommonEggs,
rarity: i18next.t("egg:defaultTier"),
});
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon1CommonEggs", eggsText); encounter.setDialogueToken("pokemon1CommonEggs", eggsText);
} }
encounter.options[0].dialogue!.buttonTooltip = pokemon1Tooltip; encounter.options[0].dialogue!.buttonTooltip = pokemon1Tooltip;
@ -158,13 +210,23 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
const [pokemon2CommonEggs, pokemon2RareEggs] = calculateEggRewardsForPokemon(pokemon2); const [pokemon2CommonEggs, pokemon2RareEggs] = calculateEggRewardsForPokemon(pokemon2);
let pokemon2Tooltip = getEncounterText(`${namespace}:option.2.tooltip_base`)!; let pokemon2Tooltip = getEncounterText(`${namespace}:option.2.tooltip_base`)!;
if (pokemon2RareEggs > 0) { if (pokemon2RareEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2RareEggs, rarity: i18next.t("egg:greatTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, {
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); count: pokemon2RareEggs,
rarity: i18next.t("egg:greatTier"),
});
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon2RareEggs", eggsText); encounter.setDialogueToken("pokemon2RareEggs", eggsText);
} }
if (pokemon2CommonEggs > 0) { if (pokemon2CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, {
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); count: pokemon2CommonEggs,
rarity: i18next.t("egg:defaultTier"),
});
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon2CommonEggs", eggsText); encounter.setDialogueToken("pokemon2CommonEggs", eggsText);
} }
encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip; encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip;
@ -173,13 +235,23 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
const [pokemon3CommonEggs, pokemon3RareEggs] = calculateEggRewardsForPokemon(pokemon3); const [pokemon3CommonEggs, pokemon3RareEggs] = calculateEggRewardsForPokemon(pokemon3);
let pokemon3Tooltip = getEncounterText(`${namespace}:option.3.tooltip_base`)!; let pokemon3Tooltip = getEncounterText(`${namespace}:option.3.tooltip_base`)!;
if (pokemon3RareEggs > 0) { if (pokemon3RareEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3RareEggs, rarity: i18next.t("egg:greatTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, {
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); count: pokemon3RareEggs,
rarity: i18next.t("egg:greatTier"),
});
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon3RareEggs", eggsText); encounter.setDialogueToken("pokemon3RareEggs", eggsText);
} }
if (pokemon3CommonEggs > 0) { if (pokemon3CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3CommonEggs, rarity: i18next.t("egg:defaultTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, {
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); count: pokemon3CommonEggs,
rarity: i18next.t("egg:defaultTier"),
});
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
eggs: eggsText,
});
encounter.setDialogueToken("pokemon3CommonEggs", eggsText); encounter.setDialogueToken("pokemon3CommonEggs", eggsText);
} }
encounter.options[2].dialogue!.buttonTooltip = pokemon3Tooltip; encounter.options[2].dialogue!.buttonTooltip = pokemon3Tooltip;
@ -193,7 +265,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
pokemon2RareEggs, pokemon2RareEggs,
pokemon3, pokemon3,
pokemon3CommonEggs, pokemon3CommonEggs,
pokemon3RareEggs pokemon3RareEggs,
}; };
return true; return true;
@ -203,8 +275,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
selected: [ selected: [
@ -224,9 +295,13 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender()); encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs); const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs);
setEncounterRewards( setEncounterRewards(
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, {
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
fillRemaining: true,
},
eggOptions, eggOptions,
() => doPostEncounterCleanup()); () => doPostEncounterCleanup(),
);
// Remove all Pokemon from the party except the chosen Pokemon // Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon1); removePokemonFromPartyAndStoreHeldItems(encounter, pokemon1);
@ -240,23 +315,26 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
]; ];
if (encounter.dialogueTokens.hasOwnProperty("pokemon1CommonEggs")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon1CommonEggs")) {
encounter.dialogue.outro.push({ 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")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon1RareEggs")) {
encounter.dialogue.outro.push({ 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; encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(config); await initBattleWithEnemyConfig(config);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
selected: [ selected: [
@ -276,9 +354,13 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender()); encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs); const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs);
setEncounterRewards( setEncounterRewards(
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, {
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
fillRemaining: true,
},
eggOptions, eggOptions,
() => doPostEncounterCleanup()); () => doPostEncounterCleanup(),
);
// Remove all Pokemon from the party except the chosen Pokemon // Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon2); removePokemonFromPartyAndStoreHeldItems(encounter, pokemon2);
@ -292,23 +374,26 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
]; ];
if (encounter.dialogueTokens.hasOwnProperty("pokemon2CommonEggs")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon2CommonEggs")) {
encounter.dialogue.outro.push({ 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")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon2RareEggs")) {
encounter.dialogue.outro.push({ 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; encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(config); await initBattleWithEnemyConfig(config);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
selected: [ selected: [
@ -328,9 +413,13 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender()); encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs); const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs);
setEncounterRewards( setEncounterRewards(
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, {
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
fillRemaining: true,
},
eggOptions, eggOptions,
() => doPostEncounterCleanup()); () => doPostEncounterCleanup(),
);
// Remove all Pokemon from the party except the chosen Pokemon // Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon3); removePokemonFromPartyAndStoreHeldItems(encounter, pokemon3);
@ -344,19 +433,23 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
]; ];
if (encounter.dialogueTokens.hasOwnProperty("pokemon3CommonEggs")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon3CommonEggs")) {
encounter.dialogue.outro.push({ 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")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon3RareEggs")) {
encounter.dialogue.outro.push({ 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; encounter.onGameOver = onGameOver;
await initBattleWithEnemyConfig(config); await initBattleWithEnemyConfig(config);
}) })
.build() .build(),
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
@ -373,12 +466,19 @@ function getPartyConfig(): EnemyPartyConfig {
breederConfig.name = i18next.t(trainerNameKey); breederConfig.name = i18next.t(trainerNameKey);
// First mon is *always* this special cleffa // 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 = { const baseConfig: EnemyPartyConfig = {
trainerType: TrainerType.EXPERT_POKEMON_BREEDER, trainerType: TrainerType.EXPERT_POKEMON_BREEDER,
pokemonConfigs: [ 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), species: getPokemonSpecies(cleffaSpecies),
isBoss: false, isBoss: false,
abilityIndex: 1, // Magic Guard abilityIndex: 1, // Magic Guard
@ -387,14 +487,17 @@ function getPartyConfig(): EnemyPartyConfig {
moveSet: [Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH], moveSet: [Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH],
ivs: [31, 31, 31, 31, 31, 31], ivs: [31, 31, 31, 31, 31, 31],
tera: PokemonType.STEEL, tera: PokemonType.STEEL,
} },
] ],
}; };
if (globalScene.arena.biomeType === Biome.SPACE) { if (globalScene.arena.biomeType === Biome.SPACE) {
// All 3 members always Cleffa line, but different configs // All 3 members always Cleffa line, but different configs
baseConfig.pokemonConfigs!.push({ baseConfig.pokemonConfigs!.push(
nickname: i18next.t(`${namespace}:cleffa_2_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }), {
nickname: i18next.t(`${namespace}:cleffa_2_nickname`, {
speciesName: getPokemonSpecies(cleffaSpecies).getName(),
}),
species: getPokemonSpecies(cleffaSpecies), species: getPokemonSpecies(cleffaSpecies),
isBoss: false, isBoss: false,
abilityIndex: 1, // Magic Guard abilityIndex: 1, // Magic Guard
@ -402,10 +505,12 @@ function getPartyConfig(): EnemyPartyConfig {
variant: 1, variant: 1,
nature: Nature.MODEST, nature: Nature.MODEST,
moveSet: [Moves.MOONBLAST, Moves.MYSTICAL_FIRE, Moves.ICE_BEAM, Moves.THUNDERBOLT], 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), species: getPokemonSpecies(cleffaSpecies),
isBoss: false, isBoss: false,
abilityIndex: 2, // Friend Guard / Unaware abilityIndex: 2, // Friend Guard / Unaware
@ -413,24 +518,27 @@ function getPartyConfig(): EnemyPartyConfig {
variant: 2, variant: 2,
nature: Nature.BOLD, nature: Nature.BOLD,
moveSet: [Moves.TRI_ATTACK, Moves.STORED_POWER, Moves.TAKE_HEART, Moves.MOONLIGHT], 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 { } else {
// Second member from pool 1 // Second member from pool 1
const pool1Species = getSpeciesFromPool(POOL_1_POKEMON, waveIndex); const pool1Species = getSpeciesFromPool(POOL_1_POKEMON, waveIndex);
// Third member from pool 2 // Third member from pool 2
const pool2Species = getSpeciesFromPool(POOL_2_POKEMON, waveIndex); const pool2Species = getSpeciesFromPool(POOL_2_POKEMON, waveIndex);
baseConfig.pokemonConfigs!.push({ baseConfig.pokemonConfigs!.push(
{
species: getPokemonSpecies(pool1Species), species: getPokemonSpecies(pool1Species),
isBoss: false, isBoss: false,
ivs: [ 31, 31, 31, 31, 31, 31 ] ivs: [31, 31, 31, 31, 31, 31],
}, },
{ {
species: getPokemonSpecies(pool2Species), species: getPokemonSpecies(pool2Species),
isBoss: false, isBoss: false,
ivs: [ 31, 31, 31, 31, 31, 31 ] ivs: [31, 31, 31, 31, 31, 31],
}); },
);
} }
return baseConfig; return baseConfig;
@ -484,7 +592,7 @@ function getEggOptions(commonEggs: number, rareEggs: number) {
pulled: false, pulled: false,
sourceType: EggSourceType.EVENT, sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription, eggDescriptor: eggDescription,
tier: EggTier.COMMON tier: EggTier.COMMON,
}); });
} }
} }
@ -494,7 +602,7 @@ function getEggOptions(commonEggs: number, rareEggs: number) {
pulled: false, pulled: false,
sourceType: EggSourceType.EVENT, sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription, eggDescriptor: eggDescription,
tier: EggTier.RARE tier: EggTier.RARE,
}); });
} }
} }
@ -508,11 +616,8 @@ function removePokemonFromPartyAndStoreHeldItems(encounter: MysteryEncounter, ch
party[chosenIndex] = party[0]; party[chosenIndex] = party[0];
party[0] = chosenPokemon; party[0] = chosenPokemon;
encounter.misc.originalParty = globalScene.getPlayerParty().slice(1); encounter.misc.originalParty = globalScene.getPlayerParty().slice(1);
encounter.misc.originalPartyHeldItems = encounter.misc.originalParty encounter.misc.originalPartyHeldItems = encounter.misc.originalParty.map(p => p.getHeldItems());
.map(p => p.getHeldItems()); globalScene["party"] = [chosenPokemon];
globalScene["party"] = [
chosenPokemon
];
} }
function restorePartyAndHeldItems() { function restorePartyAndHeldItems() {
@ -522,11 +627,11 @@ function restorePartyAndHeldItems() {
// Restore held items // Restore held items
const originalHeldItems = encounter.misc.originalPartyHeldItems; const originalHeldItems = encounter.misc.originalPartyHeldItems;
originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => { for (const pokemonHeldItemsList of originalHeldItems) {
pokemonHeldItemsList.forEach(heldItem => { for (const heldItem of pokemonHeldItemsList) {
globalScene.addModifier(heldItem, true, false, false, true); globalScene.addModifier(heldItem, true, false, false, true);
}); }
}); }
globalScene.updateModifiers(true); globalScene.updateModifiers(true);
} }
@ -571,7 +676,7 @@ function onGameOver() {
scale: 0.5, scale: 0.5,
onComplete: () => { onComplete: () => {
pokemon.leaveField(true, true, true); pokemon.leaveField(true, true, true);
} },
}); });
} }
@ -593,11 +698,10 @@ function onGameOver() {
y: "+=16", y: "+=16",
alpha: 1, alpha: 1,
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
duration: 750 duration: 750,
}); });
}); });
handleMysteryEncounterBattleFailed(true); handleMysteryEncounterBattleFailed(true);
return false; 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 { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; 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 { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; 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 type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3799 | GitHub Issue #3799}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ThePokemonSalesmanEncounter: MysteryEncounter = export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN) MysteryEncounterType.THE_POKEMON_SALESMAN,
)
.withEncounterTier(MysteryEncounterTier.ULTRA) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .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 .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", spriteKey: "pokemon_salesman",
fileRoot: "mystery-encounters", fileRoot: "mystery-encounters",
hasShadow: true hasShadow: true,
} },
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
@ -74,7 +83,11 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
} }
let pokemon: PlayerPokemon; 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 // If no HA mon found or you roll 1%, give shiny Magikarp with random variant
species = getPokemonSpecies(Species.MAGIKARP); species = getPokemonSpecies(Species.MAGIKARP);
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex, undefined, true); pokemon = new PlayerPokemon(species, 5, 2, species.formIndex, undefined, true);
@ -91,7 +104,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
repeat: true, repeat: true,
isPokemon: true, isPokemon: true,
isShiny: pokemon.shiny, isShiny: pokemon.shiny,
variant: pokemon.variant variant: pokemon.variant,
}); });
const starterTier = speciesStarterCosts[species.speciesId]; const starterTier = speciesStarterCosts[species.speciesId];
@ -109,7 +122,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
encounter.setDialogueToken("price", price.toString()); encounter.setDialogueToken("price", price.toString());
encounter.misc = { encounter.misc = {
price: price, price: price,
pokemon: pokemon pokemon: pokemon,
}; };
pokemon.calculateStats(); pokemon.calculateStats();
@ -117,8 +130,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
return true; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2 .withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
.withDialogue({ .withDialogue({
@ -127,7 +139,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
selected: [ selected: [
{ {
text: `${namespace}:option.1.selected_message`, text: `${namespace}:option.1.selected_message`,
} },
], ],
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
@ -149,7 +161,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -165,7 +177,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
// Leave encounter with no rewards or exp // Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .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. * @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
*/ */
export function getSalesmanSpeciesOffer(): PokemonSpecies { 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 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 type { PokemonHeldItemModifierType } 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 { 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3803 | GitHub Issue #3803}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const TheStrongStuffEncounter: MysteryEncounter = export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF) MysteryEncounterType.THE_STRONG_STUFF,
)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party .withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
@ -53,7 +61,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
scale: 1.25, scale: 1.25,
x: -15, x: -15,
y: 3, y: 3,
disableAnimation: true disableAnimation: true,
}, },
{ {
spriteKey: Species.SHUCKLE.toString(), spriteKey: Species.SHUCKLE.toString(),
@ -63,7 +71,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
scale: 1.25, scale: 1.25,
x: 20, x: 20,
y: 10, y: 10,
yShadow: 7 yShadow: 7,
}, },
]) // Set in onInit() ]) // Set in onInit()
.withIntroDialogue([ .withIntroDialogue([
@ -89,28 +97,30 @@ export const TheStrongStuffEncounter: MysteryEncounter =
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER], moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
modifierConfigs: [ 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, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2 stackCount: 2,
} },
], ],
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.2.stat_boost`); 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`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.1.selected` text: `${namespace}:option.1.selected`,
} },
] ],
}, },
async () => { async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!; 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) // -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 // Sort party by bst
const sortedParty = globalScene.getPlayerParty().slice(0) const sortedParty = globalScene
.getPlayerParty()
.slice(0)
.sort((pokemon1, pokemon2) => { .sort((pokemon1, pokemon2) => {
const pokemon1Bst = pokemon1.getSpeciesForm().getBaseStatTotal(); const pokemon1Bst = pokemon1.getSpeciesForm().getBaseStatTotal();
const pokemon2Bst = pokemon2.getSpeciesForm().getBaseStatTotal(); const pokemon2Bst = pokemon2.getSpeciesForm().getBaseStatTotal();
@ -170,12 +182,12 @@ export const TheStrongStuffEncounter: MysteryEncounter =
encounter.dialogue.outro = [ encounter.dialogue.outro = [
{ {
text: `${namespace}:outro`, text: `${namespace}:outro`,
} },
]; ];
setEncounterRewards({ fillRemaining: true }); setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -190,24 +202,28 @@ export const TheStrongStuffEncounter: MysteryEncounter =
async () => { async () => {
// Pick battle // Pick battle
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SOUL_DEW ], fillRemaining: true }); setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW],
fillRemaining: true,
});
encounter.startOfBattleEffects.push( encounter.startOfBattleEffects.push(
{ {
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.GASTRO_ACID), move: new PokemonMove(Moves.GASTRO_ACID),
ignorePp: true ignorePp: true,
}, },
{ {
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.STEALTH_ROCK), move: new PokemonMove(Moves.STEALTH_ROCK),
ignorePp: true ignorePp: true,
}); },
);
encounter.dialogue.outro = []; encounter.dialogue.outro = [];
await transitionMysteryEncounterIntroVisuals(true, true, 500); await transitionMysteryEncounterIntroVisuals(true, true, 500);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
} },
) )
.build(); .build();

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,12 @@ import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounte
import { MysteryEncounterBuilder } 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 { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils"; 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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type { PlayerPokemon } from "#app/field/pokemon"; 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 type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import i18next from "#app/plugins/i18n"; 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 { getLevelTotalExp } from "#app/data/exp";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { Challenges } from "#enums/challenges"; 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3822 | GitHub Issue #3822}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const WeirdDreamEncounter: MysteryEncounter = export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM) MysteryEncounterType.WEIRD_DREAM,
)
.withEncounterTier(MysteryEncounterTier.ROGUE) .withEncounterTier(MysteryEncounterTier.ROGUE)
.withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION) .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. // 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, hasShadow: true,
y: 11, y: 11,
yShadow: 6, yShadow: 6,
x: 4 x: 4,
}, },
]) ])
.withIntroDialogue([ .withIntroDialogue([
@ -153,7 +162,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets()); const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets());
globalScene.currentBattle.mysteryEncounter!.misc = { globalScene.currentBattle.mysteryEncounter!.misc = {
teamTransformations, teamTransformations,
loadAssets loadAssets,
}; };
return true; return true;
@ -163,8 +172,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
return true; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
@ -172,7 +180,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
selected: [ selected: [
{ {
text: `${namespace}:option.1.selected`, text: `${namespace}:option.1.selected`,
} },
], ],
}) })
.withPreOptionPhase(async () => { .withPreOptionPhase(async () => {
@ -215,10 +223,19 @@ export const WeirdDreamEncounter: MysteryEncounter =
await showEncounterText(`${namespace}:option.1.dream_complete`); await showEncounterText(`${namespace}:option.1.dream_complete`);
await doNewTeamPostProcess(transformations); 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); leaveEncounterWithoutBattle(true);
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -232,7 +249,8 @@ export const WeirdDreamEncounter: MysteryEncounter =
}, },
async () => { async () => {
// Battle your "future" team for some item rewards // 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 // Uses the pokemon that player's party would have transformed into
const enemyPokemonConfigs: EnemyPokemonConfig[] = []; const enemyPokemonConfigs: EnemyPokemonConfig[] = [];
@ -251,16 +269,19 @@ export const WeirdDreamEncounter: MysteryEncounter =
newPokemonHeldItemConfigs.push({ newPokemonHeldItemConfigs.push({
modifier: item.clone() as PokemonHeldItemModifier, modifier: item.clone() as PokemonHeldItemModifier,
stackCount: item.getStackCount(), stackCount: item.getStackCount(),
isTransferable: false isTransferable: false,
}); });
} }
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
if (shouldGetOldGateau(newPokemon)) { if (shouldGetOldGateau(newPokemon)) {
const stats = getOldGateauBoostedStats(newPokemon); const stats = getOldGateauBoostedStats(newPokemon);
newPokemonHeldItemConfigs.push({ 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, stackCount: 1,
isTransferable: false isTransferable: false,
}); });
} }
@ -269,19 +290,22 @@ export const WeirdDreamEncounter: MysteryEncounter =
isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD, isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD,
level: previousPokemon.level, level: previousPokemon.level,
dataSource: dataSource, dataSource: dataSource,
modifierConfigs: newPokemonHeldItemConfigs modifierConfigs: newPokemonHeldItemConfigs,
}; };
enemyPokemonConfigs.push(enemyConfig); enemyPokemonConfigs.push(enemyConfig);
} }
const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET; 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)); trainerConfig.setPartyTemplates(new TrainerPartyTemplate(transformations.length, PartyMemberStrength.STRONG));
const enemyPartyConfig: EnemyPartyConfig = { const enemyPartyConfig: EnemyPartyConfig = {
trainerConfig: trainerConfig, trainerConfig: trainerConfig,
pokemonConfigs: enemyPokemonConfigs, pokemonConfigs: enemyPokemonConfigs,
female: genderIndex === PlayerGender.FEMALE female: genderIndex === PlayerGender.FEMALE,
}; };
const onBeforeRewards = () => { 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 showEncounterText(`${namespace}:option.2.selected_2`, null, undefined, true);
await initBattleWithEnemyConfig(enemyPartyConfig); await initBattleWithEnemyConfig(enemyPartyConfig);
} },
) )
.withSimpleOption( .withSimpleOption(
{ {
@ -314,7 +352,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
async () => { async () => {
// Leave, reduce party levels by 10% // Leave, reduce party levels by 10%
for (const pokemon of globalScene.getPlayerParty()) { 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.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
pokemon.levelExp = 0; pokemon.levelExp = 0;
@ -325,7 +363,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .build();
@ -342,7 +380,7 @@ function getTeamTransformations(): PokemonTransformation[] {
const alreadyUsedSpecies: PokemonSpecies[] = party.map(p => p.species); const alreadyUsedSpecies: PokemonSpecies[] = party.map(p => p.species);
const pokemonTransformations: PokemonTransformation[] = party.map(p => { const pokemonTransformations: PokemonTransformation[] = party.map(p => {
return { return {
previousPokemon: p previousPokemon: p,
} as PokemonTransformation; } as PokemonTransformation;
}); });
@ -358,7 +396,9 @@ function getTeamTransformations(): PokemonTransformation[] {
for (let i = 0; i < numPokemon; i++) { for (let i = 0; i < numPokemon; i++) {
const removed = removedPokemon[i]; const removed = removedPokemon[i];
const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id); 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(); const bst = removed.getSpeciesForm().getBaseStatTotal();
let newBstRange: [number, number]; let newBstRange: [number, number];
@ -368,7 +408,13 @@ function getTeamTransformations(): PokemonTransformation[] {
newBstRange = STANDARD_BST_TRANSFORM_BASE_VALUES; 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(); const newSpeciesBst = newSpecies.getBaseStatTotal();
if (newSpeciesBst > SUPER_LEGENDARY_BST_THRESHOLD) { if (newSpeciesBst > SUPER_LEGENDARY_BST_THRESHOLD) {
@ -378,7 +424,6 @@ function getTeamTransformations(): PokemonTransformation[] {
hasPokemonInLegendaryBstThreshold = true; hasPokemonInLegendaryBstThreshold = true;
} }
pokemonTransformations[index].newSpecies = newSpecies; pokemonTransformations[index].newSpecies = newSpecies;
console.log("New species: " + JSON.stringify(newSpecies)); console.log("New species: " + JSON.stringify(newSpecies));
alreadyUsedSpecies.push(newSpecies); alreadyUsedSpecies.push(newSpecies);
@ -386,7 +431,12 @@ function getTeamTransformations(): PokemonTransformation[] {
for (const transformation of pokemonTransformations) { for (const transformation of pokemonTransformations) {
const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount()); 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; 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 // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
if (shouldGetOldGateau(newPokemon)) { if (shouldGetOldGateau(newPokemon)) {
const stats = getOldGateauBoostedStats(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]) .generateType(globalScene.getPlayerParty(), [OLD_GATEAU_STATS_UP, stats])
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU); ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
const modifier = modType?.newModifier(newPokemon); const modifier = modType?.newModifier(newPokemon);
@ -446,7 +497,12 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
* @param speciesRootForm * @param speciesRootForm
* @param forBattle Default `false`. If false, will perform achievements and dex unlocks for the player. * @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; let isNewStarter = false;
// Roll HA a second time // Roll HA a second time
if (newPokemon.species.abilityHidden) { 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)]; 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 // 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 (
if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) { !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); 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); const newStarterUnlocked = await globalScene.gameData.setPokemonCaught(newPokemon, true, false, false);
if (newStarterUnlocked) { if (newStarterUnlocked) {
isNewStarter = true; isNewStarter = true;
await showEncounterText(i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() })); await showEncounterText(
i18next.t("battle:addedAsAStarter", {
pokemonName: getPokemonSpecies(speciesRootForm).getName(),
}),
);
} }
} }
@ -568,20 +634,27 @@ function getOldGateauBoostedStats(pokemon: Pokemon): Stat[] {
return stats; return stats;
} }
function getTransformedSpecies(
function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies { originalBst: number,
bstSearchRange: [number, number],
hasPokemonBstHigherThan600: boolean,
hasPokemonBstBetween570And600: boolean,
alreadyUsedSpecies: PokemonSpecies[],
): PokemonSpecies {
let newSpecies: PokemonSpecies | undefined; let newSpecies: PokemonSpecies | undefined;
while (isNullOrUndefined(newSpecies)) { while (isNullOrUndefined(newSpecies)) {
const bstCap = originalBst + bstSearchRange[1]; const bstCap = originalBst + bstSearchRange[1];
const bstMin = Math.max(originalBst + bstSearchRange[0], 0); const bstMin = Math.max(originalBst + bstSearchRange[0], 0);
// Get any/all species that fall within the Bst range requirements // Get any/all species that fall within the Bst range requirements
let validSpecies = allSpecies let validSpecies = allSpecies.filter(s => {
.filter(s => {
const speciesBst = s.getBaseStatTotal(); const speciesBst = s.getBaseStatTotal();
const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap; const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap;
// Checks that a Pokemon has not already been added in the +600 or 570-600 slots; // 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); (!hasPokemonBstHigherThan600 || speciesBst <= SUPER_LEGENDARY_BST_THRESHOLD);
return bstInRange && validBst && !EXCLUDED_TRANSFORMATION_SPECIES.includes(s.speciesId); return bstInRange && validBst && !EXCLUDED_TRANSFORMATION_SPECIES.includes(s.speciesId);
}); });
@ -608,7 +681,13 @@ function doShowDreamBackground() {
transformationContainer.name = "Dream Background"; transformationContainer.name = "Dream Background";
// In case it takes a bit for video to load // 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.setName("Black Background");
transformationStaticBg.setOrigin(0, 0); transformationStaticBg.setOrigin(0, 0);
transformationContainer.add(transformationStaticBg); transformationContainer.add(transformationStaticBg);
@ -631,7 +710,7 @@ function doShowDreamBackground() {
targets: transformationContainer, targets: transformationContainer,
alpha: 1, alpha: 1,
duration: 3000, duration: 3000,
ease: "Sine.easeInOut" ease: "Sine.easeInOut",
}); });
} }
@ -645,7 +724,7 @@ function doHideDreamBackground() {
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
onComplete: () => { onComplete: () => {
globalScene.fieldUI.remove(transformationContainer, true); globalScene.fieldUI.remove(transformationContainer, true);
} },
}); });
} }
@ -660,8 +739,7 @@ function doSideBySideTransformations(transformations: PokemonTransformation[]) {
const pokemon2 = transformation.newPokemon; const pokemon2 = transformation.newPokemon;
const screenPosition = i as TransformationScreenPosition; const screenPosition = i as TransformationScreenPosition;
const transformationPromise = doPokemonTransformationSequence(pokemon1, pokemon2, screenPosition) const transformationPromise = doPokemonTransformationSequence(pokemon1, pokemon2, screenPosition).then(() => {
.then(() => {
if (transformations.length > i + 3) { if (transformations.length > i + 3) {
const nextTransformationAtPosition = transformations[i + 3]; const nextTransformationAtPosition = transformations[i + 3];
const nextPokemon1 = nextTransformationAtPosition.previousPokemon; const nextPokemon1 = nextTransformationAtPosition.previousPokemon;
@ -691,7 +769,11 @@ function doSideBySideTransformations(transformations: PokemonTransformation[]) {
* @param newPokemon * @param newPokemon
* @param speciesRootForm * @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; let eggMoveIndex: null | number = null;
const eggMoves = newPokemon.getEggMoves()?.slice(0); const eggMoves = newPokemon.getEggMoves()?.slice(0);
if (eggMoves) { 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 // 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); await globalScene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true);
} }
} }
@ -732,11 +818,18 @@ async function addEggMoveToNewPokemonMoveset(newPokemon: PlayerPokemon, speciesR
* @param newPokemonGeneratedMoveset * @param newPokemonGeneratedMoveset
* @param newEggMoveIndex * @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; let favoredMove: PokemonMove | null = null;
for (const move of newPokemonGeneratedMoveset) { for (const move of newPokemonGeneratedMoveset) {
// Needs to match first type, second type will be replaced // 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; favoredMove = move;
break; break;
} }

View File

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

View File

@ -4,13 +4,18 @@ import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import {
EncounterPokemonRequirement,
EncounterSceneRequirement,
MoneyRequirement,
TypeRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import type { CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement"; import type { CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
import { CanLearnMoveRequirement } from "./requirements/can-learn-move-requirement"; import { CanLearnMoveRequirement } from "./requirements/can-learn-move-requirement";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; 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>; 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. * Returns true if option contains any {@linkcode EncounterRequirement}s, false otherwise.
*/ */
hasRequirements(): boolean { 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 * Returns true if all {@linkcode EncounterRequirement}s for the option are met
*/ */
meetsRequirements(): boolean { meetsRequirements(): boolean {
return !this.requirements.some(requirement => !requirement.meetsRequirement()) return (
&& this.meetsSupportingRequirementAndSupportingPokemonSelected() !this.requirements.some(requirement => !requirement.meetsRequirement()) &&
&& this.meetsPrimaryRequirementAndPrimaryPokemonSelected(); this.meetsSupportingRequirementAndSupportingPokemonSelected() &&
this.meetsPrimaryRequirementAndPrimaryPokemonSelected()
);
} }
/** /**
@ -88,7 +99,13 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
* @param pokemon * @param pokemon
*/ */
pokemonMeetsPrimaryRequirements(pokemon: Pokemon): boolean { 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 { } else {
overlap.push(qp); overlap.push(qp);
} }
} }
if (truePrimaryPool.length > 0) { if (truePrimaryPool.length > 0) {
// always choose from the non-overlapping pokemon first // always choose from the non-overlapping pokemon first
this.primaryPokemon = truePrimaryPool[randSeedInt(truePrimaryPool.length)]; this.primaryPokemon = truePrimaryPool[randSeedInt(truePrimaryPool.length)];
return true; 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 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.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; 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; return false;
} }
} else {
// Just pick the first qualifying Pokemon // Just pick the first qualifying Pokemon
this.primaryPokemon = qualified[0]; this.primaryPokemon = qualified[0];
return true; return true;
} }
}
/** /**
* Returns true if all SECONDARY {@linkcode EncounterRequirement}s for the option are met, * 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[] = []; requirements: EncounterSceneRequirement[] = [];
primaryPokemonRequirements: EncounterPokemonRequirement[] = []; primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
secondaryPokemonRequirements: EncounterPokemonRequirement[] = []; secondaryPokemonRequirements: EncounterPokemonRequirement[] = [];
excludePrimaryFromSecondaryRequirements: boolean = false; excludePrimaryFromSecondaryRequirements = false;
isDisabledOnRequirementsNotMet: boolean = true; isDisabledOnRequirementsNotMet = true;
hasDexProgress: boolean = false; hasDexProgress = false;
dialogue?: OptionTextDisplay; dialogue?: OptionTextDisplay;
static newOptionWithMode(optionMode: MysteryEncounterOptionMode): MysteryEncounterOptionBuilder & Pick<IMysteryEncounterOption, "optionMode"> { static newOptionWithMode(
optionMode: MysteryEncounterOptionMode,
): MysteryEncounterOptionBuilder & Pick<IMysteryEncounterOption, "optionMode"> {
return Object.assign(new MysteryEncounterOptionBuilder(), { optionMode }); return Object.assign(new MysteryEncounterOptionBuilder(), { optionMode });
} }
@ -197,7 +215,9 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
* Adds a {@linkcode EncounterSceneRequirement} to {@linkcode requirements} * Adds a {@linkcode EncounterSceneRequirement} to {@linkcode requirements}
* @param requirement * @param requirement
*/ */
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounterOption, "requirements">> { withSceneRequirement(
requirement: EncounterSceneRequirement,
): this & Required<Pick<IMysteryEncounterOption, "requirements">> {
if (requirement instanceof EncounterPokemonRequirement) { if (requirement instanceof EncounterPokemonRequirement) {
Error("Incorrectly added pokemon requirement as scene requirement."); 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. * If there are scenarios where the Encounter should NOT continue, should return boolean instead of void.
* @param onPreOptionPhase * @param onPreOptionPhase
*/ */
withPreOptionPhase(onPreOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPreOptionPhase">> { withPreOptionPhase(
onPreOptionPhase: OptionPhaseCallback,
): this & Required<Pick<IMysteryEncounterOption, "onPreOptionPhase">> {
return Object.assign(this, { onPreOptionPhase: onPreOptionPhase }); return Object.assign(this, { onPreOptionPhase: onPreOptionPhase });
} }
@ -228,7 +250,9 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
return Object.assign(this, { onOptionPhase: onOptionPhase }); 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 }); return Object.assign(this, { onPostOptionPhase: onPostOptionPhase });
} }
@ -236,13 +260,17 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
* Adds a {@linkcode EncounterPokemonRequirement} to {@linkcode primaryPokemonRequirements} * Adds a {@linkcode EncounterPokemonRequirement} to {@linkcode primaryPokemonRequirements}
* @param requirement * @param requirement
*/ */
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounterOption, "primaryPokemonRequirements">> { withPrimaryPokemonRequirement(
requirement: EncounterPokemonRequirement,
): this & Required<Pick<IMysteryEncounterOption, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) { if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement."); Error("Incorrectly added scene requirement as pokemon requirement.");
} }
this.primaryPokemonRequirements.push(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 * @param invertQuery
* @returns * @returns
*/ */
withPokemonTypeRequirement(type: PokemonType | PokemonType[], excludeFainted?: boolean, minNumberOfPokemon?: number, invertQuery?: boolean) { withPokemonTypeRequirement(
return this.withPrimaryPokemonRequirement(new TypeRequirement(type, excludeFainted, minNumberOfPokemon, invertQuery)); 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 requirement
* @param excludePrimaryFromSecondaryRequirements * @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) { if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement."); Error("Incorrectly added scene requirement as pokemon requirement.");
} }
this.secondaryPokemonRequirements.push(requirement); this.secondaryPokemonRequirements.push(requirement);
this.excludePrimaryFromSecondaryRequirements = excludePrimaryFromSecondaryRequirements; this.excludePrimaryFromSecondaryRequirements = excludePrimaryFromSecondaryRequirements;
return Object.assign(this, { secondaryPokemonRequirements: this.secondaryPokemonRequirements }); return Object.assign(this, {
secondaryPokemonRequirements: this.secondaryPokemonRequirements,
});
} }
/** /**

View File

@ -76,7 +76,7 @@ export class CombinationSceneRequirement extends EncounterSceneRequirement {
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
if (this.isAnd) { if (this.isAnd) {
throw new Error("Not implemented (Sorry)"); throw new Error("Not implemented (Sorry)");
} else { }
for (const req of this.requirements) { for (const req of this.requirements) {
if (req.meetsRequirement()) { if (req.meetsRequirement()) {
return req.getDialogueToken(pokemon); return req.getDialogueToken(pokemon);
@ -86,7 +86,6 @@ export class CombinationSceneRequirement extends EncounterSceneRequirement {
return this.requirements[0].getDialogueToken(pokemon); return this.requirements[0].getDialogueToken(pokemon);
} }
} }
}
export abstract class EncounterPokemonRequirement implements EncounterRequirement { export abstract class EncounterPokemonRequirement implements EncounterRequirement {
public minNumberOfPokemon: number; public minNumberOfPokemon: number;
@ -153,11 +152,10 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (this.isAnd) { if (this.isAnd) {
return this.requirements.reduce((relevantPokemon, req) => req.queryParty(relevantPokemon), partyPokemon); return this.requirements.reduce((relevantPokemon, req) => req.queryParty(relevantPokemon), partyPokemon);
} else { }
const matchingRequirement = this.requirements.find(req => req.queryParty(partyPokemon).length > 0); const matchingRequirement = this.requirements.find(req => req.queryParty(partyPokemon).length > 0);
return matchingRequirement ? matchingRequirement.queryParty(partyPokemon) : []; return matchingRequirement ? matchingRequirement.queryParty(partyPokemon) : [];
} }
}
/** /**
* Retrieves a dialogue token key/value pair for the given {@linkcode EncounterPokemonRequirement | requirements}. * 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] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
if (this.isAnd) { if (this.isAnd) {
throw new Error("Not implemented (Sorry)"); throw new Error("Not implemented (Sorry)");
} else { }
for (const req of this.requirements) { for (const req of this.requirements) {
if (req.meetsRequirement()) { if (req.meetsRequirement()) {
return req.getDialogueToken(pokemon); return req.getDialogueToken(pokemon);
@ -178,7 +176,6 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
return this.requirements[0].getDialogueToken(pokemon); return this.requirements[0].getDialogueToken(pokemon);
} }
} }
}
export class PreviousEncounterRequirement extends EncounterSceneRequirement { export class PreviousEncounterRequirement extends EncounterSceneRequirement {
previousEncounterRequirement: MysteryEncounterType; previousEncounterRequirement: MysteryEncounterType;
@ -193,11 +190,18 @@ export class PreviousEncounterRequirement extends EncounterSceneRequirement {
} }
override meetsRequirement(): boolean { 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] { override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return [ "previousEncounter", globalScene.mysteryEncounterSaveData.encounteredEvents.find(e => e.type === this.previousEncounterRequirement)?.[0].toString() ?? "" ]; 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 { override meetsRequirement(): boolean {
if (!isNullOrUndefined(this.waveRange) && this.waveRange[0] <= this.waveRange[1]) { if (!isNullOrUndefined(this.waveRange) && this.waveRange[0] <= this.waveRange[1]) {
const waveIndex = globalScene.currentBattle.waveIndex; 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 false;
} }
} }
return true; return true;
} }
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return ["waveIndex", globalScene.currentBattle.waveIndex.toString()]; return ["waveIndex", globalScene.currentBattle.waveIndex.toString()];
} }
} }
@ -253,7 +260,7 @@ export class WaveModulusRequirement extends EncounterSceneRequirement {
return this.waveModuli.includes(globalScene.currentBattle.waveIndex % this.modulusValue); 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()]; return ["waveIndex", globalScene.currentBattle.waveIndex.toString()];
} }
} }
@ -268,14 +275,18 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
const timeOfDay = globalScene.arena?.getTimeOfDay(); 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 false;
} }
return true; return true;
} }
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return ["timeOfDay", TimeOfDay[globalScene.arena.getTimeOfDay()].toLocaleLowerCase()]; return ["timeOfDay", TimeOfDay[globalScene.arena.getTimeOfDay()].toLocaleLowerCase()];
} }
} }
@ -290,14 +301,18 @@ export class WeatherRequirement extends EncounterSceneRequirement {
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
const currentWeather = globalScene.arena.weather?.weatherType; 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 false;
} }
return true; return true;
} }
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
const currentWeather = globalScene.arena.weather?.weatherType; const currentWeather = globalScene.arena.weather?.weatherType;
let token = ""; let token = "";
if (!isNullOrUndefined(currentWeather)) { if (!isNullOrUndefined(currentWeather)) {
@ -325,8 +340,13 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange[0] <= this.partySizeRange[1]) { if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange[0] <= this.partySizeRange[1]) {
const partySize = this.excludeDisallowedPokemon ? globalScene.getPokemonAllowedInBattle().length : globalScene.getPlayerParty().length; const partySize = this.excludeDisallowedPokemon
if (partySize >= 0 && (this.partySizeRange[0] >= 0 && this.partySizeRange[0] > partySize) || (this.partySizeRange[1] >= 0 && this.partySizeRange[1] < partySize)) { ? 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; return false;
} }
} }
@ -334,7 +354,7 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
return true; return true;
} }
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return ["partySize", globalScene.getPlayerParty().length.toString()]; return ["partySize", globalScene.getPlayerParty().length.toString()];
} }
} }
@ -343,7 +363,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
requiredHeldItemModifiers: string[]; requiredHeldItemModifiers: string[];
minNumberOfItems: number; minNumberOfItems: number;
constructor(heldItem: string | string[], minNumberOfItems: number = 1) { constructor(heldItem: string | string[], minNumberOfItems = 1) {
super(); super();
this.minNumberOfItems = minNumberOfItems; this.minNumberOfItems = minNumberOfItems;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem]; this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
@ -355,19 +375,19 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
return false; return false;
} }
let modifierCount = 0; let modifierCount = 0;
this.requiredHeldItemModifiers.forEach(modifier => { for (const modifier of this.requiredHeldItemModifiers) {
const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier); const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier);
if (matchingMods?.length > 0) { if (matchingMods?.length > 0) {
matchingMods.forEach(matchingMod => { for (const matchingMod of matchingMods) {
modifierCount += matchingMod.stackCount; modifierCount += matchingMod.stackCount;
});
} }
}); }
}
return modifierCount >= this.minNumberOfItems; return modifierCount >= this.minNumberOfItems;
} }
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return ["requiredItem", this.requiredHeldItemModifiers[0]]; return ["requiredItem", this.requiredHeldItemModifiers[0]];
} }
} }
@ -394,8 +414,11 @@ export class MoneyRequirement extends EncounterSceneRequirement {
return !(this.requiredMoney > 0 && this.requiredMoney > money); return !(this.requiredMoney > 0 && this.requiredMoney > money);
} }
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
const value = this.scalingMultiplier > 0 ? globalScene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString(); const value =
this.scalingMultiplier > 0
? globalScene.getWaveMoneyAmount(this.scalingMultiplier).toString()
: this.requiredMoney.toString();
return ["money", value]; return ["money", value];
} }
} }
@ -405,7 +428,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
constructor(species: Species | Species[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(species: Species | Species[], minNumberOfPokemon = 1, invertQuery = false) {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -422,11 +445,14 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredSpecies.filter((species) => pokemon.species.speciesId === species).length > 0); return partyPokemon.filter(
} else { 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);
} }
// 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] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -437,13 +463,12 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
} }
} }
export class NatureRequirement extends EncounterPokemonRequirement { export class NatureRequirement extends EncounterPokemonRequirement {
requiredNature: Nature[]; requiredNature: Nature[];
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
constructor(nature: Nature | Nature[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(nature: Nature | Nature[], minNumberOfPokemon = 1, invertQuery = false) {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -460,11 +485,10 @@ export class NatureRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
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);
} 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);
} }
// 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] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -481,7 +505,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
constructor(type: PokemonType | PokemonType[], excludeFainted: boolean = true, minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(type: PokemonType | PokemonType[], excludeFainted = true, minNumberOfPokemon = 1, invertQuery = false) {
super(); super();
this.excludeFainted = excludeFainted; this.excludeFainted = excludeFainted;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
@ -497,7 +521,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
} }
if (this.excludeFainted) { if (this.excludeFainted) {
partyPokemon = partyPokemon.filter((pokemon) => !pokemon.isFainted()); partyPokemon = partyPokemon.filter(pokemon => !pokemon.isFainted());
} }
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
@ -505,15 +529,18 @@ export class TypeRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredType.filter((type) => pokemon.getTypes().includes(type)).length > 0); return partyPokemon.filter(
} else { 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);
} }
// 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] { 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) { if (includedTypes.length > 0) {
return ["type", PokemonType[includedTypes[0]]]; return ["type", PokemonType[includedTypes[0]]];
} }
@ -521,14 +548,13 @@ export class TypeRequirement extends EncounterPokemonRequirement {
} }
} }
export class MoveRequirement extends EncounterPokemonRequirement { export class MoveRequirement extends EncounterPokemonRequirement {
requiredMoves: Moves[] = []; requiredMoves: Moves[] = [];
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
excludeDisallowedPokemon: 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(); super();
this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
@ -547,25 +573,27 @@ export class MoveRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
// get the Pokemon with at least one move in the required moves list // get the Pokemon with at least one move in the required moves list
return partyPokemon.filter((pokemon) => return partyPokemon.filter(
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) pokemon =>
&& pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId))); (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) &&
} else { 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)));
} }
// 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] { 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]) { if (includedMoves && includedMoves.length > 0 && includedMoves[0]) {
return ["move", includedMoves[0].getName()]; return ["move", includedMoves[0].getName()];
} }
return ["move", ""]; return ["move", ""];
} }
} }
/** /**
@ -578,7 +606,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
constructor(learnableMove: Moves | Moves[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(learnableMove: Moves | Moves[], minNumberOfPokemon = 1, invertQuery = false) {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -595,21 +623,31 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { 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); return partyPokemon.filter(
} else { pokemon =>
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed learnableMoves this.requiredMoves.filter(learnableMove =>
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((learnableMove) => pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m?.moveId === tm)).includes(learnableMove)).length === 0); 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] { 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) { if (includedCompatMoves.length > 0) {
return ["compatibleMove", Moves[includedCompatMoves[0]]]; return ["compatibleMove", Moves[includedCompatMoves[0]]];
} }
return ["compatibleMove", ""]; return ["compatibleMove", ""];
} }
} }
export class AbilityRequirement extends EncounterPokemonRequirement { export class AbilityRequirement extends EncounterPokemonRequirement {
@ -618,7 +656,12 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
invertQuery: boolean; invertQuery: boolean;
excludeDisallowedPokemon: 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(); super();
this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
@ -636,15 +679,18 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => return partyPokemon.filter(
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) pokemon =>
&& this.requiredAbilities.some((ability) => pokemon.hasAbility(ability, false))); (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) &&
} else { 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);
} }
// 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] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -661,7 +707,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
constructor(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon = 1, invertQuery = false) {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -680,35 +726,42 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => { return partyPokemon.filter(pokemon => {
return this.requiredStatusEffect.some((statusEffect) => { return this.requiredStatusEffect.some(statusEffect => {
if (statusEffect === StatusEffect.NONE) { if (statusEffect === StatusEffect.NONE) {
// StatusEffect.NONE also checks for null or undefined status // StatusEffect.NONE also checks for null or undefined status
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === statusEffect; return (
} else { isNullOrUndefined(pokemon.status) ||
return pokemon.status?.effect === statusEffect; 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 // 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 partyPokemon.filter(pokemon => {
return !this.requiredStatusEffect.some((statusEffect) => { return !this.requiredStatusEffect.some(statusEffect => {
if (statusEffect === StatusEffect.NONE) { if (statusEffect === StatusEffect.NONE) {
// StatusEffect.NONE also checks for null or undefined status // StatusEffect.NONE also checks for null or undefined status
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === statusEffect; return (
} else { isNullOrUndefined(pokemon.status) ||
isNullOrUndefined(pokemon.status.effect) ||
pokemon.status.effect === statusEffect
);
}
return pokemon.status?.effect === statusEffect; return pokemon.status?.effect === statusEffect;
}
}); });
}); });
} }
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const reqStatus = this.requiredStatusEffect.filter((a) => { const reqStatus = this.requiredStatusEffect.filter(a => {
if (a === StatusEffect.NONE) { 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; return pokemon!.status?.effect === a;
}); });
@ -717,7 +770,6 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
} }
return ["status", ""]; return ["status", ""];
} }
} }
/** /**
@ -730,7 +782,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
constructor(formChangeItem: FormChangeItem | FormChangeItem[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(formChangeItem: FormChangeItem | FormChangeItem[], minNumberOfPokemon = 1, invertQuery = false) {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -746,35 +798,44 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
} }
filterByForm(pokemon, formChangeItem) { 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 // 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 // Returns true if any form changes match this item
.map(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger) .flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger)
.flat().flatMap(fc => fc.item).includes(formChangeItem)) { .flatMap(fc => fc.item)
.includes(formChangeItem)
) {
return true; return true;
} else {
return false;
} }
return false;
} }
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredFormChangeItem.filter((formChangeItem) => this.filterByForm(pokemon, formChangeItem)).length > 0); return partyPokemon.filter(
} else { pokemon =>
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed formChangeItems 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] { 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) { if (requiredItems.length > 0) {
return ["formChangeItem", FormChangeItem[requiredItems[0]]]; return ["formChangeItem", FormChangeItem[requiredItems[0]]];
} }
return ["formChangeItem", ""]; return ["formChangeItem", ""];
} }
} }
export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement { export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
@ -782,7 +843,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
constructor(evolutionItems: EvolutionItem | EvolutionItem[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(evolutionItems: EvolutionItem | EvolutionItem[], minNumberOfPokemon = 1, invertQuery = false) {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -798,11 +859,23 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
} }
filterByEvo(pokemon, evolutionItem) { filterByEvo(pokemon, evolutionItem) {
if (pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && pokemonEvolutions[pokemon.species.speciesId].filter(e => e.item === evolutionItem if (
&& (!e.condition || e.condition.predicate(pokemon))).length && (pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX)) { 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; 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 true;
} }
return false; return false;
@ -810,15 +883,20 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredEvolutionItem.filter((evolutionItem) => this.filterByEvo(pokemon, evolutionItem)).length > 0); return partyPokemon.filter(
} else { pokemon =>
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed evolutionItemss this.requiredEvolutionItem.filter(evolutionItem => this.filterByEvo(pokemon, evolutionItem)).length > 0,
return partyPokemon.filter((pokemon) => this.requiredEvolutionItem.filter((evolutionItems) => this.filterByEvo(pokemon, evolutionItems)).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] { 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) { if (requiredItems.length > 0) {
return ["evolutionItem", EvolutionItem[requiredItems[0]]]; return ["evolutionItem", EvolutionItem[requiredItems[0]]];
} }
@ -832,7 +910,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
invertQuery: boolean; invertQuery: boolean;
requireTransferable: 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(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -850,25 +928,33 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredHeldItemModifiers.some((heldItem) => { return partyPokemon.filter(pokemon =>
return pokemon.getHeldItems().some((it) => { this.requiredHeldItemModifiers.some(heldItem => {
return pokemon.getHeldItems().some(it => {
return it.constructor.name === heldItem && (!this.requireTransferable || it.isTransferable); 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 // 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 // E.g. functions as a blacklist
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => { return partyPokemon.filter(
return !this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) pokemon =>
&& (!this.requireTransferable || it.isTransferable); pokemon.getHeldItems().filter(it => {
}).length > 0); return (
} !this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) &&
(!this.requireTransferable || it.isTransferable)
);
}).length > 0,
);
} }
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = pokemon?.getHeldItems().filter((it) => { const requiredItems = pokemon?.getHeldItems().filter(it => {
return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) return (
&& (!this.requireTransferable || it.isTransferable); this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) &&
(!this.requireTransferable || it.isTransferable)
);
}); });
if (requiredItems && requiredItems.length > 0) { if (requiredItems && requiredItems.length > 0) {
return ["heldItem", requiredItems[0].type.name]; return ["heldItem", requiredItems[0].type.name];
@ -883,7 +969,12 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
invertQuery: boolean; invertQuery: boolean;
requireTransferable: boolean; requireTransferable: boolean;
constructor(heldItemTypes: PokemonType | PokemonType[], minNumberOfPokemon: number = 1, invertQuery: boolean = false, requireTransferable: boolean = true) { constructor(
heldItemTypes: PokemonType | PokemonType[],
minNumberOfPokemon = 1,
invertQuery = false,
requireTransferable = true,
) {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -901,31 +992,43 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredHeldItemTypes.some((heldItemType) => { return partyPokemon.filter(pokemon =>
return pokemon.getHeldItems().some((it) => { this.requiredHeldItemTypes.some(heldItemType => {
return it instanceof AttackTypeBoosterModifier return pokemon.getHeldItems().some(it => {
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType return (
&& (!this.requireTransferable || it.isTransferable); 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 // 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 // E.g. functions as a blacklist
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => { return partyPokemon.filter(
return !this.requiredHeldItemTypes.some(heldItemType => pokemon =>
it instanceof AttackTypeBoosterModifier pokemon.getHeldItems().filter(it => {
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType return !this.requiredHeldItemTypes.some(
&& (!this.requireTransferable || it.isTransferable)); heldItemType =>
}).length > 0); it instanceof AttackTypeBoosterModifier &&
} (it.type as AttackTypeBoosterModifierType).moveType === heldItemType &&
(!this.requireTransferable || it.isTransferable),
);
}).length > 0,
);
} }
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = pokemon?.getHeldItems().filter((it) => { const requiredItems = pokemon?.getHeldItems().filter(it => {
return this.requiredHeldItemTypes.some(heldItemType => return (
it instanceof AttackTypeBoosterModifier this.requiredHeldItemTypes.some(
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType) heldItemType =>
&& (!this.requireTransferable || it.isTransferable); it instanceof AttackTypeBoosterModifier &&
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType,
) &&
(!this.requireTransferable || it.isTransferable)
);
}); });
if (requiredItems && requiredItems.length > 0) { if (requiredItems && requiredItems.length > 0) {
return ["heldItem", requiredItems[0].type.name]; return ["heldItem", requiredItems[0].type.name];
@ -939,7 +1042,7 @@ export class LevelRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
constructor(requiredLevelRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(requiredLevelRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -960,11 +1063,14 @@ export class LevelRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => pokemon.level >= this.requiredLevelRange[0] && pokemon.level <= this.requiredLevelRange[1]); return partyPokemon.filter(
} else { 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]);
} }
// 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] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -977,7 +1083,7 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
constructor(requiredFriendshipRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(requiredFriendshipRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -986,7 +1092,10 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
// Party Pokemon inside required friendship range // 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 partyPokemon = globalScene.getPlayerParty();
const pokemonInRange = this.queryParty(partyPokemon); const pokemonInRange = this.queryParty(partyPokemon);
if (pokemonInRange.length < this.minNumberOfPokemon) { if (pokemonInRange.length < this.minNumberOfPokemon) {
@ -998,11 +1107,17 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => pokemon.friendship >= this.requiredFriendshipRange[0] && pokemon.friendship <= this.requiredFriendshipRange[1]); return partyPokemon.filter(
} else { pokemon =>
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredFriendshipRanges pokemon.friendship >= this.requiredFriendshipRange[0] &&
return partyPokemon.filter((pokemon) => pokemon.friendship < this.requiredFriendshipRange[0] || pokemon.friendship > this.requiredFriendshipRange[1]); 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] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -1020,7 +1135,7 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
constructor(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(requiredHealthRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -1041,13 +1156,17 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => { return partyPokemon.filter(pokemon => {
return pokemon.getHpRatio() >= this.requiredHealthRange[0] && pokemon.getHpRatio() <= this.requiredHealthRange[1]; 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] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
@ -1064,7 +1183,7 @@ export class WeightRequirement extends EncounterPokemonRequirement {
minNumberOfPokemon: number; minNumberOfPokemon: number;
invertQuery: boolean; invertQuery: boolean;
constructor(requiredWeightRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { constructor(requiredWeightRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
@ -1085,15 +1204,18 @@ export class WeightRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => pokemon.getWeight() >= this.requiredWeightRange[0] && pokemon.getWeight() <= this.requiredWeightRange[1]); return partyPokemon.filter(
} else { pokemon =>
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredWeightRanges 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] { override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return ["weight", pokemon?.getWeight().toString() ?? ""]; 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 { OptionPhaseCallback } from "./mystery-encounter-option";
import type MysteryEncounterOption from "./mystery-encounter-option"; import type MysteryEncounterOption from "./mystery-encounter-option";
import { MysteryEncounterOptionBuilder } 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 type { BattlerIndex } from "#app/battle";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
@ -278,7 +285,10 @@ export default class MysteryEncounter implements IMysteryEncounter {
this.dialogue = this.dialogue ?? {}; this.dialogue = this.dialogue ?? {};
this.spriteConfigs = this.spriteConfigs ? [...this.spriteConfigs] : []; this.spriteConfigs = this.spriteConfigs ? [...this.spriteConfigs] : [];
// Default max is 1 for ROGUE encounters, 2 for others // 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.encounterMode = MysteryEncounterMode.DEFAULT;
this.requirements = this.requirements ? this.requirements : []; this.requirements = this.requirements ? this.requirements : [];
this.hideBattleIntroMessage = this.hideBattleIntroMessage ?? false; this.hideBattleIntroMessage = this.hideBattleIntroMessage ?? false;
@ -317,7 +327,13 @@ export default class MysteryEncounter implements IMysteryEncounter {
* @param pokemon * @param pokemon
*/ */
pokemonMeetsPrimaryRequirements(pokemon: Pokemon): boolean { 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 { } else {
overlap.push(qp); overlap.push(qp);
} }
} }
if (truePrimaryPool.length > 0) { if (truePrimaryPool.length > 0) {
// Always choose from the non-overlapping pokemon first // Always choose from the non-overlapping pokemon first
this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)]; this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)];
return true; 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 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? // is this working?
this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)]; 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; 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; 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 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)]; this.primaryPokemon = qualified[Utils.randSeedInt(qualified.length, 0)];
return true; return true;
} }
}
/** /**
* Returns true if all SECONDARY {@linkcode EncounterRequirement}s for the option are met, * 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[]]; options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]];
enemyPartyConfigs: EnemyPartyConfig[] = []; enemyPartyConfigs: EnemyPartyConfig[] = [];
localizationKey: string = ""; localizationKey = "";
dialogue: MysteryEncounterDialogue = {}; dialogue: MysteryEncounterDialogue = {};
requirements: EncounterSceneRequirement[] = []; requirements: EncounterSceneRequirement[] = [];
primaryPokemonRequirements: EncounterPokemonRequirement[] = []; primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
secondaryPokemonRequirements: EncounterPokemonRequirement[] = []; secondaryPokemonRequirements: EncounterPokemonRequirement[] = [];
excludePrimaryFromSupportRequirements: boolean = true; excludePrimaryFromSupportRequirements = true;
dialogueTokens: Record<string, string> = {}; dialogueTokens: Record<string, string> = {};
hideBattleIntroMessage: boolean = false; hideBattleIntroMessage = false;
autoHideIntroVisuals: boolean = true; autoHideIntroVisuals = true;
enterIntroVisualsFromRight: boolean = false; enterIntroVisualsFromRight = false;
continuousEncounter: boolean = false; continuousEncounter = false;
catchAllowed: boolean = false; catchAllowed = false;
fleeAllowed: boolean = true; fleeAllowed = true;
lockEncounterRewardTiers: boolean = false; lockEncounterRewardTiers = false;
startOfBattleEffectsComplete: boolean = false; startOfBattleEffectsComplete = false;
hasBattleAnimationsWithoutTargets: boolean = false; hasBattleAnimationsWithoutTargets = false;
skipEnemyBattleTurns: boolean = false; skipEnemyBattleTurns = false;
skipToFightInput: boolean = false; skipToFightInput = false;
preventGameStatsUpdates: boolean = false; preventGameStatsUpdates = false;
maxAllowedEncounters: number = 3; maxAllowedEncounters = 3;
expMultiplier: number = 1; expMultiplier = 1;
/** /**
* REQUIRED * REQUIRED
@ -566,7 +581,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param encounterType * @param encounterType
* @returns this * @returns this
*/ */
static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & Pick<IMysteryEncounter, "encounterType"> { static withEncounterType(
encounterType: MysteryEncounterType,
): MysteryEncounterBuilder & Pick<IMysteryEncounter, "encounterType"> {
return Object.assign(new MysteryEncounterBuilder(), { encounterType }); return Object.assign(new MysteryEncounterBuilder(), { encounterType });
} }
@ -582,10 +599,31 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
if (!this.options) { if (!this.options) {
const options = [option]; const options = [option];
return Object.assign(this, { options }); return Object.assign(this, { options });
} else { }
this.options.push(option); this.options.push(option);
return this; 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} * @param callback {@linkcode OptionPhaseCallback}
* @returns * @returns
*/ */
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> { withSimpleDexProgressOption(
return this.withOption(MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build()); dialogue: OptionTextDisplay,
} callback: OptionPhaseCallback,
): this & Pick<IMysteryEncounter, "options"> {
/** return this.withOption(
* Defines an option + phasefor the encounter. MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
* 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)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue(dialogue) .withDialogue(dialogue)
.withOptionPhase(callback).build()); .withOptionPhase(callback)
.build(),
);
} }
/** /**
@ -626,7 +655,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param spriteConfigs * @param spriteConfigs
* @returns * @returns
*/ */
withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick<IMysteryEncounter, "spriteConfigs"> { withIntroSpriteConfigs(
spriteConfigs: MysteryEncounterSpriteConfig[],
): this & Pick<IMysteryEncounter, "spriteConfigs"> {
return Object.assign(this, { spriteConfigs: spriteConfigs }); return Object.assign(this, { spriteConfigs: spriteConfigs });
} }
@ -635,7 +666,13 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
return this; return this;
} }
withIntro({ spriteConfigs, dialogue } : {spriteConfigs: MysteryEncounterSpriteConfig[], dialogue?: MysteryEncounterDialogue["intro"]}) { withIntro({
spriteConfigs,
dialogue,
}: {
spriteConfigs: MysteryEncounterSpriteConfig[];
dialogue?: MysteryEncounterDialogue["intro"];
}) {
return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue); return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue);
} }
@ -676,7 +713,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param encounterAnimations * @param encounterAnimations
* @returns * @returns
*/ */
withAnimations(...encounterAnimations: EncounterAnim[]): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> { withAnimations(
...encounterAnimations: EncounterAnim[]
): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations]; const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
return Object.assign(this, { encounterAnimations: animations }); return Object.assign(this, { encounterAnimations: animations });
} }
@ -686,7 +725,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @returns * @returns
* @param disallowedGameModes * @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]; const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes];
return Object.assign(this, { disallowedGameModes: gameModes }); return Object.assign(this, { disallowedGameModes: gameModes });
} }
@ -696,7 +737,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @returns * @returns
* @param disallowedChallenges * @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]; const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges];
return Object.assign(this, { disallowedChallenges: challenges }); return Object.assign(this, { disallowedChallenges: challenges });
} }
@ -707,7 +750,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* Default false * Default false
* @param continuousEncounter * @param continuousEncounter
*/ */
withContinuousEncounter(continuousEncounter: boolean): this & Required<Pick<IMysteryEncounter, "continuousEncounter">> { withContinuousEncounter(
continuousEncounter: boolean,
): this & Required<Pick<IMysteryEncounter, "continuousEncounter">> {
return Object.assign(this, { continuousEncounter: continuousEncounter }); return Object.assign(this, { continuousEncounter: continuousEncounter });
} }
@ -717,7 +762,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* Default false * Default false
* @param hasBattleAnimationsWithoutTargets * @param hasBattleAnimationsWithoutTargets
*/ */
withBattleAnimationsWithoutTargets(hasBattleAnimationsWithoutTargets: boolean): this & Required<Pick<IMysteryEncounter, "hasBattleAnimationsWithoutTargets">> { withBattleAnimationsWithoutTargets(
hasBattleAnimationsWithoutTargets: boolean,
): this & Required<Pick<IMysteryEncounter, "hasBattleAnimationsWithoutTargets">> {
return Object.assign(this, { hasBattleAnimationsWithoutTargets }); return Object.assign(this, { hasBattleAnimationsWithoutTargets });
} }
@ -727,7 +774,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* Default false * Default false
* @param skipEnemyBattleTurns * @param skipEnemyBattleTurns
*/ */
withSkipEnemyBattleTurns(skipEnemyBattleTurns: boolean): this & Required<Pick<IMysteryEncounter, "skipEnemyBattleTurns">> { withSkipEnemyBattleTurns(
skipEnemyBattleTurns: boolean,
): this & Required<Pick<IMysteryEncounter, "skipEnemyBattleTurns">> {
return Object.assign(this, { 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 * If true, will prevent updating {@linkcode GameStats} for encountering and/or defeating Pokemon
* Default `false` * Default `false`
*/ */
withPreventGameStatsUpdates(preventGameStatsUpdates: boolean): this & Required<Pick<IMysteryEncounter, "preventGameStatsUpdates">> { withPreventGameStatsUpdates(
preventGameStatsUpdates: boolean,
): this & Required<Pick<IMysteryEncounter, "preventGameStatsUpdates">> {
return Object.assign(this, { preventGameStatsUpdates }); return Object.assign(this, { preventGameStatsUpdates });
} }
@ -753,7 +804,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param maxAllowedEncounters * @param maxAllowedEncounters
* @returns * @returns
*/ */
withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required<Pick<IMysteryEncounter, "maxAllowedEncounters">> { withMaxAllowedEncounters(
maxAllowedEncounters: number,
): this & Required<Pick<IMysteryEncounter, "maxAllowedEncounters">> {
return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters }); return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters });
} }
@ -764,7 +817,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param requirement * @param requirement
* @returns * @returns
*/ */
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounter, "requirements">> { withSceneRequirement(
requirement: EncounterSceneRequirement,
): this & Required<Pick<IMysteryEncounter, "requirements">> {
if (requirement instanceof EncounterPokemonRequirement) { if (requirement instanceof EncounterPokemonRequirement) {
Error("Incorrectly added pokemon requirement as scene requirement."); 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 * @param excludeDisallowedPokemon if true, only counts allowed (legal in Challenge/unfainted) mons
* @returns * @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)); return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeDisallowedPokemon));
} }
@ -801,13 +860,17 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param requirement {@linkcode EncounterPokemonRequirement} * @param requirement {@linkcode EncounterPokemonRequirement}
* @returns * @returns
*/ */
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> { withPrimaryPokemonRequirement(
requirement: EncounterPokemonRequirement,
): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) { if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement."); Error("Incorrectly added scene requirement as pokemon requirement.");
} }
this.primaryPokemonRequirements.push(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 * @param invertQuery if true will invert the query
* @returns * @returns
*/ */
withPrimaryPokemonStatusEffectRequirement(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> { withPrimaryPokemonStatusEffectRequirement(
return this.withPrimaryPokemonRequirement(new StatusEffectRequirement(statusEffect, minNumberOfPokemon, invertQuery)); 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 * @param invertQuery if true will invert the query
* @returns * @returns
*/ */
withPrimaryPokemonHealthRatioRequirement(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> { withPrimaryPokemonHealthRatioRequirement(
return this.withPrimaryPokemonRequirement(new HealthRatioRequirement(requiredHealthRange, minNumberOfPokemon, invertQuery)); 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? // 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 // 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 // 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) { if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement."); Error("Incorrectly added scene requirement as pokemon requirement.");
} }
this.secondaryPokemonRequirements.push(requirement); this.secondaryPokemonRequirements.push(requirement);
this.excludePrimaryFromSupportRequirements = excludePrimaryFromSecondaryRequirements; 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 * @param hideBattleIntroMessage If `true`, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter
* @returns * @returns
*/ */
withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required<Pick<IMysteryEncounter, "hideBattleIntroMessage">> { withHideWildIntroMessage(
return Object.assign(this, { hideBattleIntroMessage: hideBattleIntroMessage }); 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 * @param autoHideIntroVisuals If `false`, will not hide the intro visuals that are displayed at the beginning of encounter
* @returns * @returns
*/ */
withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required<Pick<IMysteryEncounter, "autoHideIntroVisuals">> { withAutoHideIntroVisuals(
autoHideIntroVisuals: boolean,
): this & Required<Pick<IMysteryEncounter, "autoHideIntroVisuals">> {
return Object.assign(this, { autoHideIntroVisuals: autoHideIntroVisuals }); return Object.assign(this, { autoHideIntroVisuals: autoHideIntroVisuals });
} }
@ -936,8 +1023,12 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* Default false * Default false
* @returns * @returns
*/ */
withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required<Pick<IMysteryEncounter, "enterIntroVisualsFromRight">> { withEnterIntroVisualsFromRight(
return Object.assign(this, { enterIntroVisualsFromRight: enterIntroVisualsFromRight }); 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: {
...encounterOptionsDialogue, ...encounterOptionsDialogue,
title, title,
} },
}; };
return this; return this;
@ -974,7 +1065,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
encounterOptionsDialogue: { encounterOptionsDialogue: {
...encounterOptionsDialogue, ...encounterOptionsDialogue,
description, description,
} },
}; };
return this; return this;
@ -994,7 +1085,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
encounterOptionsDialogue: { encounterOptionsDialogue: {
...encounterOptionsDialogue, ...encounterOptionsDialogue,
query, query,
} },
}; };
return this; return this;

View File

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

View File

@ -40,7 +40,9 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
} }
override meetsRequirement(): boolean { 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) { if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
return false; return false;
@ -51,17 +53,16 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => return partyPokemon.filter(pokemon =>
// every required move should be included // every required move should be included
this.requiredMoves.every((requiredMove) => this.getAllPokemonMoves(pokemon).includes(requiredMove)) 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))
); );
} }
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] { override getDialogueToken(__pokemon?: PlayerPokemon): [string, string] {

View File

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

View File

@ -63,7 +63,13 @@ export function queueEncounterMessage(contentKey: string): void {
* @param callbackDelay * @param callbackDelay
* @param promptDelay * @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 => { return new Promise<void>(resolve => {
const text: string | null = getEncounterText(contentKey); const text: string | null = getEncounterText(contentKey);
globalScene.ui.showText(text ?? "", delay, () => resolve(), callbackDelay, prompt, promptDelay); 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 speakerContentKey
* @param callbackDelay * @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 => { return new Promise<void>(resolve => {
const text: string | null = getEncounterText(textContentKey); const text: string | null = getEncounterText(textContentKey);
const speaker: string | null = getEncounterText(speakerContentKey); 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 { BattlerIndex, BattleType } from "#app/battle";
import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes"; import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes";
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; 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 { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import type { AiType, PlayerPokemon } from "#app/field/pokemon"; import type { AiType, PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, FieldPosition, PokemonMove, PokemonSummonData } from "#app/field/pokemon"; import { EnemyPokemon, FieldPosition, PokemonMove, PokemonSummonData } from "#app/field/pokemon";
import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type";
import { getPartyLuckValue, ModifierPoolType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import {
import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; 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 PokemonData from "#app/system/pokemon-data";
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
@ -71,7 +86,7 @@ export function doTrainerExclamation() {
globalScene.time.delayedCall(800, () => { globalScene.time.delayedCall(800, () => {
globalScene.field.remove(exclamationSprite, true); globalScene.field.remove(exclamationSprite, true);
}); });
} },
}); });
globalScene.playSound("battle_anims/GEN8- Exclaim", { volume: 0.7 }); globalScene.playSound("battle_anims/GEN8- Exclaim", { volume: 0.7 });
@ -151,8 +166,15 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle); const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle);
doubleBattle = doubleTrainer; doubleBattle = doubleTrainer;
const trainerFemale = isNullOrUndefined(partyConfig.female) ? !!(Utils.randSeedInt(2)) : partyConfig.female; 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 newTrainer = new Trainer(
trainerConfig.trainerType,
doubleTrainer ? TrainerVariant.DOUBLE : trainerFemale ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
undefined,
undefined,
undefined,
trainerConfig,
);
newTrainer.x += 300; newTrainer.x += 300;
newTrainer.setVisible(false); newTrainer.setVisible(false);
globalScene.field.add(newTrainer); globalScene.field.add(newTrainer);
@ -163,7 +185,12 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
} else { } else {
// Wild // Wild
globalScene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.WILD_BATTLE; 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()); 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 = battle.enemyLevels.map(level => level + additive);
battle.enemyLevels.forEach((level, e) => { battle.enemyLevels.forEach((level, e) => {
let enemySpecies; let enemySpecies: PokemonSpecies | undefined;
let dataSource; let dataSource: PokemonData | undefined;
let isBoss = false; let isBoss = false;
if (!loaded) { if (!loaded) {
if ((!isNullOrUndefined(trainerType) || trainerConfig) && battle.trainer) { if ((!isNullOrUndefined(trainerType) || trainerConfig) && battle.trainer) {
@ -195,7 +222,14 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
dataSource = config.dataSource; dataSource = config.dataSource;
enemySpecies = config.species; enemySpecies = config.species;
isBoss = config.isBoss; 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 { } else {
battle.enemyParty[e] = battle.trainer.genPartyMember(e); battle.enemyParty[e] = battle.trainer.genPartyMember(e);
} }
@ -213,7 +247,14 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
enemySpecies = globalScene.randomSpecies(battle.waveIndex, level, true); 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(); enemyPokemon.resetSummonData();
} }
if (!loaded && isNullOrUndefined(partyConfig.countAsSeen) || partyConfig.countAsSeen) { if ((!loaded && isNullOrUndefined(partyConfig.countAsSeen)) || partyConfig.countAsSeen) {
globalScene.gameData.setPokemonSeen(enemyPokemon, true, !!(trainerType || trainerConfig)); globalScene.gameData.setPokemonSeen(enemyPokemon, true, !!(trainerType || trainerConfig));
} }
@ -268,7 +309,9 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
// Set Boss // Set Boss
if (config.isBoss) { 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)) { if (!isNullOrUndefined(config.bossSegmentModifier)) {
segments += config.bossSegmentModifier; segments += config.bossSegmentModifier;
} }
@ -295,7 +338,11 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
if (statusEffects) { if (statusEffects) {
// Default to cureturn 3 for sleep // Default to cureturn 3 for sleep
const status = Array.isArray(statusEffects) ? statusEffects[0] : statusEffects; 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); enemyPokemon.status = new Status(status, 0, cureTurn);
} }
@ -368,7 +415,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
` Spd: ${enemyPokemon.stats[5]} (${enemyPokemon.ivs[5]})`, ` Spd: ${enemyPokemon.stats[5]} (${enemyPokemon.ivs[5]})`,
]; ];
const moveset: string[] = []; const moveset: string[] = [];
enemyPokemon.getMoveset().forEach((move) => { enemyPokemon.getMoveset().forEach(move => {
moveset.push(move!.getName()); // TODO: remove `!` after moveset-null removal PR moveset.push(move!.getName()); // TODO: remove `!` after moveset-null removal PR
}); });
@ -381,7 +428,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
console.log( console.log(
`Ability: ${enemyPokemon.getAbility().name}`, `Ability: ${enemyPokemon.getAbility().name}`,
`| Passive Ability${enemyPokemon.hasPassive() ? "" : " (inactive)"}: ${enemyPokemon.getPassiveAbility().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); console.log("Moveset:", moveset);
}); });
@ -400,7 +447,10 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
} }
}); });
if (!loaded) { 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 const customModifierTypes = partyConfig?.pokemonConfigs
?.filter(config => config?.modifierConfigs) ?.filter(config => config?.modifierConfigs)
.map(config => config.modifierConfigs!); .map(config => config.modifierConfigs!);
@ -417,8 +467,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
*/ */
export function loadCustomMovesForEncounter(moves: Moves | Moves[]) { export function loadCustomMovesForEncounter(moves: Moves | Moves[]) {
moves = Array.isArray(moves) ? moves : [moves]; moves = Array.isArray(moves) ? moves : [moves];
return Promise.all(moves.map(move => initMoveAnim(move))) return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves));
.then(() => loadMoveAnimAssets(moves));
} }
/** /**
@ -427,7 +476,7 @@ export function loadCustomMovesForEncounter(moves: Moves | Moves[]) {
* @param playSound * @param playSound
* @param showMessage * @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.money = Math.min(Math.max(globalScene.money + changeValue, 0), Number.MAX_SAFE_INTEGER);
globalScene.updateMoneyText(); globalScene.updateMoneyText();
globalScene.animateMoneyChanged(false); globalScene.animateMoneyChanged(false);
@ -436,9 +485,21 @@ export function updatePlayerMoney(changeValue: number, playSound: boolean = true
} }
if (showMessage) { if (showMessage) {
if (changeValue < 0) { 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 { } 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]) .withIdFromFunc(modifierTypes[modifierId])
.withTierFromPool(ModifierPoolType.PLAYER, globalScene.getPlayerParty()); .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 modifier
* @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. * @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); const result = generateModifierType(modifier, pregenArgs);
if (result) { if (result) {
return new ModifierTypeOption(result, 0); 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 onPokemonNotSelected - Any logic that needs to be performed if no Pokemon is chosen
* @param selectablePokemonFilter * @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 => { return new Promise(resolve => {
const modeToSetOnExit = globalScene.ui.getMode(); const modeToSetOnExit = globalScene.ui.getMode();
// Open party screen to choose pokemon // 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) { if (slotIndex < globalScene.getPlayerParty().length) {
globalScene.ui.setMode(modeToSetOnExit).then(() => { globalScene.ui.setMode(modeToSetOnExit).then(() => {
const pokemon = globalScene.getPlayerParty()[slotIndex]; const pokemon = globalScene.getPlayerParty()[slotIndex];
const secondaryOptions = onPokemonSelected(pokemon); const secondaryOptions = onPokemonSelected(pokemon);
if (!secondaryOptions) { if (!secondaryOptions) {
globalScene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); globalScene.currentBattle.mysteryEncounter!.setDialogueToken(
"selectedPokemon",
pokemon.getNameToRender(),
);
resolve(true); resolve(true);
return; return;
} }
@ -504,17 +582,22 @@ export function selectPokemonForOption(onPokemonSelected: (pokemon: PlayerPokemo
globalScene.ui.setMode(Mode.MESSAGE).then(() => { globalScene.ui.setMode(Mode.MESSAGE).then(() => {
const displayOptions = () => { const displayOptions = () => {
// Always appends a cancel option to bottom of options // Always appends a cancel option to bottom of options
const fullOptions = secondaryOptions.map(option => { const fullOptions = secondaryOptions
.map(option => {
// Update handler to resolve promise // Update handler to resolve promise
const onSelect = option.handler; const onSelect = option.handler;
option.handler = () => { option.handler = () => {
onSelect(); onSelect();
globalScene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); globalScene.currentBattle.mysteryEncounter!.setDialogueToken(
"selectedPokemon",
pokemon.getNameToRender(),
);
resolve(true); resolve(true);
return true; return true;
}; };
return option; return option;
}).concat({ })
.concat({
label: i18next.t("menu:cancel"), label: i18next.t("menu:cancel"),
handler: () => { handler: () => {
globalScene.ui.clearText(); globalScene.ui.clearText();
@ -524,14 +607,14 @@ export function selectPokemonForOption(onPokemonSelected: (pokemon: PlayerPokemo
}, },
onHover: () => { onHover: () => {
showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false); showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false);
} },
}); });
const config: OptionSelectConfig = { const config: OptionSelectConfig = {
options: fullOptions, options: fullOptions,
maxOptions: 7, maxOptions: 7,
yOffset: 0, yOffset: 0,
supportHover: true supportHover: true,
}; };
// Do hover over the starting selection option // 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); 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) { if (!textPromptKey) {
displayOptions(); displayOptions();
} else { } else {
@ -557,7 +641,9 @@ export function selectPokemonForOption(onPokemonSelected: (pokemon: PlayerPokemo
resolve(false); resolve(false);
}); });
} }
}, selectablePokemonFilter); },
selectablePokemonFilter,
);
}); });
} }
@ -575,7 +661,12 @@ interface PokemonAndOptionSelected {
* @param selectablePokemonFilter * @param selectablePokemonFilter
* @param onHoverOverCancelOption * @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 => { return new Promise<PokemonAndOptionSelected | null>(resolve => {
const modeToSetOnExit = globalScene.ui.getMode(); const modeToSetOnExit = globalScene.ui.getMode();
@ -601,22 +692,32 @@ export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelec
const selectPokemonAfterOption = (selectedOptionIndex: number) => { const selectPokemonAfterOption = (selectedOptionIndex: number) => {
// Open party screen to choose a Pokemon // 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) { if (slotIndex < globalScene.getPlayerParty().length) {
// Pokemon and option selected // Pokemon and option selected
globalScene.ui.setMode(modeToSetOnExit).then(() => { globalScene.ui.setMode(modeToSetOnExit).then(() => {
const result: PokemonAndOptionSelected = { selectedPokemonIndex: slotIndex, selectedOptionIndex: selectedOptionIndex }; const result: PokemonAndOptionSelected = {
selectedPokemonIndex: slotIndex,
selectedOptionIndex: selectedOptionIndex,
};
resolve(result); resolve(result);
}); });
} else { } else {
// Back to first option select screen // Back to first option select screen
displayOptions(config); displayOptions(config);
} }
}, selectablePokemonFilter); },
selectablePokemonFilter,
);
}; };
// Always appends a cancel option to bottom of options // 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 // Update handler to resolve promise
const onSelect = option.handler; const onSelect = option.handler;
option.handler = () => { option.handler = () => {
@ -625,7 +726,8 @@ export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelec
return true; return true;
}; };
return option; return option;
}).concat({ })
.concat({
label: i18next.t("menu:cancel"), label: i18next.t("menu:cancel"),
handler: () => { handler: () => {
globalScene.ui.clearText(); globalScene.ui.clearText();
@ -638,14 +740,14 @@ export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelec
onHoverOverCancelOption(); onHoverOverCancelOption();
} }
showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false); showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false);
} },
}); });
const config: OptionSelectConfig = { const config: OptionSelectConfig = {
options: fullOptions, options: fullOptions,
maxOptions: 7, maxOptions: 7,
yOffset: 0, yOffset: 0,
supportHover: true supportHover: true,
}; };
displayOptions(config); displayOptions(config);
@ -659,7 +761,11 @@ export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelec
* @param eggRewards * @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}) * @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 = () => { globalScene.currentBattle.mysteryEncounter!.doEncounterRewards = () => {
if (preRewardsCallback) { if (preRewardsCallback) {
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) * 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 * @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]; const participantIds = Array.isArray(participantId) ? participantId : [participantId];
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => { 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 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) * @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.currentBattle.mysteryEncounter!.encounterMode = encounterMode;
globalScene.clearPhaseQueue(); globalScene.clearPhaseQueue();
globalScene.clearPhaseQueueSplice(); 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 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 * @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) { export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinue = false) {
const allowedPkm = globalScene.getPlayerParty().filter((pkm) => pkm.isAllowedInBattle()); const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
if (allowedPkm.length === 0) { if (allowedPkm.length === 0) {
globalScene.clearPhaseQueue(); globalScene.clearPhaseQueue();
@ -763,10 +872,17 @@ export function handleMysteryEncounterVictory(addHealPhase: boolean = false, doN
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
if (encounter.continuousEncounter || doNotContinue) { if (encounter.continuousEncounter || doNotContinue) {
return; return;
} else if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) { }
if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase));
globalScene.pushPhase(new EggLapsePhase()); 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)); globalScene.pushPhase(new BattleEndPhase(true));
if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
globalScene.pushPhase(new TrainerVictoryPhase()); 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 * Similar to {@linkcode handleMysteryEncounterVictory}, but for cases where the player lost a battle or failed a challenge
* @param addHealPhase * @param addHealPhase
*/ */
export function handleMysteryEncounterBattleFailed(addHealPhase: boolean = false, doNotContinue: boolean = false) { export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotContinue = false) {
const allowedPkm = globalScene.getPlayerParty().filter((pkm) => pkm.isAllowedInBattle()); const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
if (allowedPkm.length === 0) { if (allowedPkm.length === 0) {
globalScene.clearPhaseQueue(); globalScene.clearPhaseQueue();
@ -799,7 +915,8 @@ export function handleMysteryEncounterBattleFailed(addHealPhase: boolean = false
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
if (encounter.continuousEncounter || doNotContinue) { if (encounter.continuousEncounter || doNotContinue) {
return; return;
} else if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) { }
if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
globalScene.pushPhase(new BattleEndPhase(false)); 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 destroy - If true, will destroy visuals ONLY ON HIDE TRANSITION. Does nothing on show. Defaults to true
* @param duration * @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 => { return new Promise(resolve => {
const introVisuals = globalScene.currentBattle.mysteryEncounter!.introVisuals; const introVisuals = globalScene.currentBattle.mysteryEncounter!.introVisuals;
const enemyPokemon = globalScene.getEnemyField(); const enemyPokemon = globalScene.getEnemyField();
@ -852,7 +969,7 @@ export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, des
globalScene.currentBattle.mysteryEncounter!.introVisuals = undefined; globalScene.currentBattle.mysteryEncounter!.introVisuals = undefined;
} }
resolve(true); resolve(true);
} },
}); });
} else { } else {
resolve(true); resolve(true);
@ -866,10 +983,15 @@ export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, des
*/ */
export function handleMysteryEncounterBattleStartEffects() { export function handleMysteryEncounterBattleStartEffects() {
const encounter = globalScene.currentBattle.mysteryEncounter; 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; const effects = encounter.startOfBattleEffects;
effects.forEach(effect => { effects.forEach(effect => {
let source; let source: EnemyPokemon | Pokemon;
if (effect.sourcePokemon) { if (effect.sourcePokemon) {
source = effect.sourcePokemon; source = effect.sourcePokemon;
} else if (!isNullOrUndefined(effect.sourceBattlerIndex)) { } else if (!isNullOrUndefined(effect.sourceBattlerIndex)) {
@ -887,6 +1009,7 @@ export function handleMysteryEncounterBattleStartEffects() {
} else { } else {
source = globalScene.getEnemyField()[0]; source = globalScene.getEnemyField()[0];
} }
// @ts-ignore: source cannot be undefined
globalScene.pushPhase(new MovePhase(source, effect.targets, effect.move, effect.followUp, effect.ignorePp)); 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 * @param rerollHidden whether the mon should get an extra roll for Hidden Ability
* @returns {@linkcode EnemyPokemon} for the requested encounter * @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 bossSpecies: PokemonSpecies;
let isEventEncounter = false; let isEventEncounter = false;
const eventEncounters = globalScene.eventManager.getEventEncounters(); const eventEncounters = globalScene.eventManager.getEventEncounters();
let formIndex; let formIndex: number | undefined;
if (eventEncounters.length > 0 && randSeedInt(2) === 1) { if (eventEncounters.length > 0 && randSeedInt(2) === 1) {
const eventEncounter = randSeedItem(eventEncounters); 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; isEventEncounter = true;
bossSpecies = getPokemonSpecies(levelSpecies); bossSpecies = getPokemonSpecies(levelSpecies);
formIndex = eventEncounter.formIndex; formIndex = eventEncounter.formIndex;
} else { } 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); const ret = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, isBoss);
if (formIndex) { if (formIndex) {
@ -959,8 +1093,17 @@ export function getRandomEncounterSpecies(level: number, isBoss: boolean = false
export function calculateMEAggregateStats(baseSpawnWeight: number) { export function calculateMEAggregateStats(baseSpawnWeight: number) {
const numRuns = 1000; const numRuns = 1000;
let run = 0; let run = 0;
const biomes = Object.keys(Biome).filter(key => isNaN(Number(key))); 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 alwaysPickTheseBiomes = [
Biome.ISLAND,
Biome.ABYSS,
Biome.WASTELAND,
Biome.FAIRY_CAVE,
Biome.TEMPLE,
Biome.LABORATORY,
Biome.SPACE,
Biome.WASTELAND,
];
const calculateNumEncounters = (): any[] => { const calculateNumEncounters = (): any[] => {
let encounterRate = baseSpawnWeight; // BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT let encounterRate = baseSpawnWeight; // BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
@ -987,7 +1130,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
.filter(b => { .filter(b => {
return !Array.isArray(b) || !Utils.randSeedInt(b[1]); 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); }, i * 100);
if (biomes! && biomes.length > 0) { if (biomes! && biomes.length > 0) {
const specialBiomes = biomes.filter(b => alwaysPickTheseBiomes.includes(b)); const specialBiomes = biomes.filter(b => alwaysPickTheseBiomes.includes(b));
@ -998,7 +1141,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
} }
} }
} else if (biomeLinks.hasOwnProperty(currentBiome)) { } else if (biomeLinks.hasOwnProperty(currentBiome)) {
currentBiome = (biomeLinks[currentBiome] as Biome); currentBiome = biomeLinks[currentBiome] as Biome;
} else { } else {
if (!(i % 50)) { if (!(i % 50)) {
currentBiome = Biome.END; 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 // If total number of encounters is lower than expected for the run, slightly favor a new encounter
// Do the reverse as well // 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 currentRunDiffFromAvg = expectedEncountersByFloor - numEncounters.reduce((a, b) => a + b);
const favoredEncounterRate = encounterRate + currentRunDiffFromAvg * 15; const favoredEncounterRate = encounterRate + currentRunDiffFromAvg * 15;
// If the most recent ME was 3 or fewer waves ago, can never spawn a ME // 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) { if (canSpawn && roll < favoredEncounterRate) {
mostRecentEncounterWave = i; mostRecentEncounterWave = i;
@ -1052,7 +1195,13 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
const uncommonThreshold = totalWeight - tierWeights[0] - tierWeights[1]; // 64 - 32 - 16 = 16 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 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); encountersByBiome.set(Biome[currentBiome], (encountersByBiome.get(Biome[currentBiome]) ?? 0) + 1);
} else { } else {
encounterRate += WEIGHT_INCREMENT_ON_SPAWN_MISS; 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`; 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]); const meanEncountersPerRunPerBiomeSorted = [...meanEncountersPerRunPerBiome.entries()].sort(
meanEncountersPerRunPerBiomeSorted.forEach(value => stats = stats + `${value[0]}: avg valid floors ${meanMEFloorsPerRunPerBiome.get(value[0])}, avg MEs ${value[1]},\n`); (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); console.log(stats);
} }
/** /**
* TODO: remove once encounter spawn rate is finalized * TODO: remove once encounter spawn rate is finalized
* Just a helper function to calculate aggregate stats for MEs in a Classic run * 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 // Roll boss tier
// luck influences encounter rarity // luck influences encounter rarity
let luckModifier = 0; let luckModifier = 0;
if (!isNaN(luckValue)) { if (!Number.isNaN(luckValue)) {
luckModifier = luckValue * 0.5; luckModifier = luckValue * 0.5;
} }
const tierValue = Utils.randSeedInt(64 - luckModifier); 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) { switch (tier) {
default:
case BiomePoolTier.BOSS:
++bossEncountersByRarity[0];
break;
case BiomePoolTier.BOSS_RARE: case BiomePoolTier.BOSS_RARE:
++bossEncountersByRarity[1]; ++bossEncountersByRarity[1];
break; break;
@ -1153,6 +1309,10 @@ export function calculateRareSpawnAggregateStats(luckValue: number) {
case BiomePoolTier.BOSS_ULTRA_RARE: case BiomePoolTier.BOSS_ULTRA_RARE:
++bossEncountersByRarity[3]; ++bossEncountersByRarity[3];
break; 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 { PokemonHeldItemModifier } from "#app/modifier/modifier";
import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type Pokemon 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 { PlayerGender } from "#enums/player-gender";
import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims";
import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect";
@ -17,7 +22,11 @@ import type { PokemonType } from "#enums/pokemon-type";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters"; 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 { getPokemonNameWithAffix } from "#app/messages";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } 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 shiny
* @param variant * @param variant
*/ */
export function getSpriteKeysFromSpecies(species: Species, female?: boolean, formIndex?: number, shiny?: boolean, variant?: number): { spriteKey: string, fileRoot: string } { export function getSpriteKeysFromSpecies(
const spriteKey = getPokemonSpecies(species).getSpriteKey(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0); species: Species,
const fileRoot = getPokemonSpecies(species).getSpriteAtlasPath(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0); 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 }; return { spriteKey, fileRoot };
} }
/** /**
* Gets the sprite key and file root for a given Pokemon (accounts for gender, shiny, variants, forms, and experimental) * 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 } { export function getSpriteKeysFromPokemon(pokemon: Pokemon): {
const spriteKey = pokemon.getSpeciesForm().getSpriteKey(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant); spriteKey: string;
const fileRoot = pokemon.getSpeciesForm().getSpriteAtlasPath(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant); 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 }; 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) * @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 * @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(); const party = globalScene.getPlayerParty();
let chosenIndex: number; let chosenIndex: number;
let chosenPokemon: PlayerPokemon | null = null; 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. * @param isFainted Default false. If true, includes fainted mons.
* @returns * @returns
*/ */
export function getHighestLevelPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon { export function getHighestLevelPlayerPokemon(isAllowed = false, isFainted = false): PlayerPokemon {
const party = globalScene.getPlayerParty(); const party = globalScene.getPlayerParty();
let pokemon: PlayerPokemon | null = null; let pokemon: PlayerPokemon | null = null;
@ -116,7 +152,7 @@ export function getHighestLevelPlayerPokemon(isAllowed: boolean = false, isFaint
continue; continue;
} }
pokemon = pokemon ? pokemon?.level < p?.level ? p : pokemon : p; pokemon = pokemon ? (pokemon?.level < p?.level ? p : pokemon) : p;
} }
return pokemon!; return pokemon!;
@ -130,7 +166,7 @@ export function getHighestLevelPlayerPokemon(isAllowed: boolean = false, isFaint
* @param isFainted Default false. If true, includes fainted mons. * @param isFainted Default false. If true, includes fainted mons.
* @returns * @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(); const party = globalScene.getPlayerParty();
let pokemon: PlayerPokemon | null = null; let pokemon: PlayerPokemon | null = null;
@ -142,7 +178,7 @@ export function getHighestStatPlayerPokemon(stat: PermanentStat, isAllowed: bool
continue; 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!; return pokemon!;
@ -155,7 +191,7 @@ export function getHighestStatPlayerPokemon(stat: PermanentStat, isAllowed: bool
* @param isFainted Default false. If true, includes fainted mons. * @param isFainted Default false. If true, includes fainted mons.
* @returns * @returns
*/ */
export function getLowestLevelPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon { export function getLowestLevelPlayerPokemon(isAllowed = false, isFainted = false): PlayerPokemon {
const party = globalScene.getPlayerParty(); const party = globalScene.getPlayerParty();
let pokemon: PlayerPokemon | null = null; let pokemon: PlayerPokemon | null = null;
@ -167,7 +203,7 @@ export function getLowestLevelPlayerPokemon(isAllowed: boolean = false, isFainte
continue; continue;
} }
pokemon = pokemon ? pokemon?.level > p?.level ? p : pokemon : p; pokemon = pokemon ? (pokemon?.level > p?.level ? p : pokemon) : p;
} }
return pokemon!; return pokemon!;
@ -180,7 +216,7 @@ export function getLowestLevelPlayerPokemon(isAllowed: boolean = false, isFainte
* @param isFainted Default false. If true, includes fainted mons. * @param isFainted Default false. If true, includes fainted mons.
* @returns * @returns
*/ */
export function getHighestStatTotalPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon { export function getHighestStatTotalPlayerPokemon(isAllowed = false, isFainted = false): PlayerPokemon {
const party = globalScene.getPlayerParty(); const party = globalScene.getPlayerParty();
let pokemon: PlayerPokemon | null = null; let pokemon: PlayerPokemon | null = null;
@ -192,7 +228,7 @@ export function getHighestStatTotalPlayerPokemon(isAllowed: boolean = false, isF
continue; 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!; return pokemon!;
@ -209,28 +245,40 @@ export function getHighestStatTotalPlayerPokemon(isAllowed: boolean = false, isF
* @param allowMythical * @param allowMythical
* @returns * @returns
*/ */
export function getRandomSpeciesByStarterCost(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: PokemonType[], 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 min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers;
let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers; let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers;
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarterCosts) 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 => { .filter(s => {
const pokemonSpecies = getPokemonSpecies(s[0]); const pokemonSpecies = getPokemonSpecies(s[0]);
return pokemonSpecies && (!excludedSpecies || !excludedSpecies.includes(s[0])) return (
&& (allowSubLegendary || !pokemonSpecies.subLegendary) pokemonSpecies &&
&& (allowLegendary || !pokemonSpecies.legendary) (!excludedSpecies || !excludedSpecies.includes(s[0])) &&
&& (allowMythical || !pokemonSpecies.mythical); (allowSubLegendary || !pokemonSpecies.subLegendary) &&
(allowLegendary || !pokemonSpecies.legendary) &&
(allowMythical || !pokemonSpecies.mythical)
);
}) })
.map(s => [getPokemonSpecies(s[0]), s[1]]); .map(s => [getPokemonSpecies(s[0]), s[1]]);
if (types && types.length > 0) { 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 // 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 // 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)) { while (tryFilterStarterTiers.length === 0 && !(min === 0 && max === 10)) {
if (min > 0) { if (min > 0) {
min--; min--;
@ -259,7 +307,11 @@ export function koPlayerPokemon(pokemon: PlayerPokemon) {
pokemon.hp = 0; pokemon.hp = 0;
pokemon.trySetStatus(StatusEffect.FAINT); pokemon.trySetStatus(StatusEffect.FAINT);
pokemon.updateInfo(); 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) { export function applyDamageToPokemon(pokemon: PlayerPokemon, damage: number) {
if (damage <= 0) { 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 a Pokemon would faint from the damage applied, its HP is instead set to 1.
if (pokemon.isAllowedInBattle() && pokemon.hp - damage <= 0) { 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) { export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) {
if (heal <= 0) { 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); applyHpChangeToPokemon(pokemon, heal);
@ -321,7 +377,8 @@ export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) {
* @param value * @param value
*/ */
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) { 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]) .generateType(globalScene.getPlayerParty(), [value])
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE); ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE);
const modifier = modType?.newModifier(pokemon); const modifier = modType?.newModifier(pokemon);
@ -339,15 +396,20 @@ export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: numb
* @param modType * @param modType
* @param fallbackModifierType * @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 // Check if the Pokemon has max stacks of that item already
const modifier = modType.newModifier(pokemon); const modifier = modType.newModifier(pokemon);
const existing = globalScene.findModifier(m => ( const existing = globalScene.findModifier(
m =>
m instanceof PokemonHeldItemModifier && m instanceof PokemonHeldItemModifier &&
m.type.id === modType.id && m.type.id === modType.id &&
m.pokemonId === pokemon.id && m.pokemonId === pokemon.id &&
m.matchType(modifier) m.matchType(modifier),
)) as PokemonHeldItemModifier; ) as PokemonHeldItemModifier;
// At max stacks // At max stacks
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
@ -373,7 +435,11 @@ export async function applyModifierTypeToPlayerPokemon(pokemon: PlayerPokemon, m
* @param pokeballType * @param pokeballType
* @param ballTwitchRate - can pass custom ball catch rates (for special events, like safari) * @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; const originalY: number = pokemon.y;
if (!ballTwitchRate) { if (!ballTwitchRate) {
@ -397,7 +463,9 @@ export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: Pokeba
}); });
return new Promise(resolve => { 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.time.delayedCall(512, () => {
globalScene.playSound("se/pb_throw"); globalScene.playSound("se/pb_throw");
@ -406,7 +474,9 @@ export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: Pokeba
globalScene.time.delayedCall(256, () => { globalScene.time.delayedCall(256, () => {
globalScene.trainer.setFrame("3"); globalScene.trainer.setFrame("3");
globalScene.time.delayedCall(768, () => { 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, alpha: 0,
duration: 200, duration: 200,
easing: "Sine.easeIn", easing: "Sine.easeIn",
onComplete: () => pbTint.destroy() onComplete: () => pbTint.destroy(),
}); });
} },
}); });
} }
}, },
onComplete: () => { onComplete: () => {
catchPokemon(pokemon, pokeball, pokeballType).then(() => resolve(true)); catchPokemon(pokemon, pokeball, pokeballType).then(() => resolve(true));
} },
}); });
}; };
globalScene.time.delayedCall(250, () => doPokeballBounceAnim(pokeball, 16, 72, 350, doShake)); globalScene.time.delayedCall(250, () => doPokeballBounceAnim(pokeball, 16, 72, 350, doShake));
} },
}); });
} },
}); });
}); });
}); });
@ -515,7 +585,12 @@ export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: Pokeba
* @param pokeball * @param pokeball
* @param pokeballType * @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 => { return new Promise<void>(resolve => {
globalScene.playSound("se/pb_rel"); globalScene.playSound("se/pb_rel");
pokemon.setY(originalY); pokemon.setY(originalY);
@ -534,13 +609,21 @@ function failCatch(pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.Ga
targets: pokemon, targets: pokemon,
duration: 250, duration: 250,
ease: "Sine.easeOut", ease: "Sine.easeOut",
scale: 1 scale: 1,
}); });
globalScene.currentBattle.lastUsedPokeball = pokeballType; globalScene.currentBattle.lastUsedPokeball = pokeballType;
removePb(pokeball); 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 showCatchObtainMessage
* @param isObtain * @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(); 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); globalScene.validateAchv(achvs.HIDDEN_ABILITY);
} }
@ -614,17 +706,47 @@ export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameO
Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => { Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => {
if (globalScene.getPlayerParty().length === 6) { if (globalScene.getPlayerParty().length === 6) {
const promptRelease = () => { 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.pokemonInfoContainer.makeRoomForConfirmUi(1, true);
globalScene.ui.setMode(Mode.CONFIRM, () => { globalScene.ui.setMode(
const newPokemon = globalScene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon); Mode.CONFIRM,
globalScene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => { () => {
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(() => { globalScene.ui.setMode(Mode.MESSAGE).then(() => {
promptRelease(); promptRelease();
}); });
}, false); },
}, () => { false,
globalScene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: number, _option: PartyOption) => { );
},
() => {
globalScene.ui.setMode(
Mode.PARTY,
PartyUiMode.RELEASE,
0,
(slotIndex: number, _option: PartyOption) => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => { globalScene.ui.setMode(Mode.MESSAGE).then(() => {
if (slotIndex < 6) { if (slotIndex < 6) {
addToParty(slotIndex); addToParty(slotIndex);
@ -632,14 +754,19 @@ export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameO
promptRelease(); promptRelease();
} }
}); });
}); },
}, () => { );
},
() => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => { globalScene.ui.setMode(Mode.MESSAGE).then(() => {
removePokemon(); removePokemon();
end(); end();
}); });
}, "fullParty"); },
}); "fullParty",
);
},
);
}; };
promptRelease(); promptRelease();
} else { } else {
@ -649,7 +776,15 @@ export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameO
}; };
if (showCatchObtainMessage) { 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 { } else {
doPokemonCatchMenu(); doPokemonCatchMenu();
} }
@ -671,7 +806,7 @@ function removePb(pokeball: Phaser.GameObjects.Sprite) {
alpha: 0, alpha: 0,
onComplete: () => { onComplete: () => {
pokeball.destroy(); pokeball.destroy();
} },
}); });
} }
} }
@ -696,11 +831,17 @@ export async function doPokemonFlee(pokemon: EnemyPokemon): Promise<void> {
onComplete: () => { onComplete: () => {
pokemon.setVisible(false); pokemon.setVisible(false);
pokemon.leaveField(true, true, true); pokemon.leaveField(true, true, true);
showEncounterText(i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false) showEncounterText(
.then(() => { i18next.t("battle:pokemonFled", {
pokemonName: pokemon.getNameToRender(),
}),
null,
600,
false,
).then(() => {
resolve(); resolve();
}); });
} },
}); });
}); });
} }
@ -724,11 +865,17 @@ export function doPlayerFlee(pokemon: EnemyPokemon): Promise<void> {
onComplete: () => { onComplete: () => {
pokemon.setVisible(false); pokemon.setVisible(false);
pokemon.leaveField(true, true, true); pokemon.leaveField(true, true, true);
showEncounterText(i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false) showEncounterText(
.then(() => { i18next.t("battle:playerFled", {
pokemonName: pokemon.getNameToRender(),
}),
null,
600,
false,
).then(() => {
resolve(); resolve();
}); });
} },
}); });
}); });
} }
@ -792,7 +939,7 @@ export function getGoldenBugNetSpecies(level: number): PokemonSpecies {
* @param scene * @param scene
* @param levelAdditiveModifier Default 0. will add +(1 level / 10 waves * levelAdditiveModifier) to the level calculation * @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 currentBattle = globalScene.currentBattle;
const baseLevel = currentBattle.getLevelForWave(); const baseLevel = currentBattle.getLevelForWave();
@ -803,7 +950,10 @@ export function getEncounterPokemonLevelForWave(levelAdditiveModifier: number =
export async function addPokemonDataToDexAndValidateAchievements(pokemon: PlayerPokemon) { export async function addPokemonDataToDexAndValidateAchievements(pokemon: PlayerPokemon) {
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); 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); globalScene.validateAchv(achvs.HIDDEN_ABILITY);
} }
@ -832,9 +982,16 @@ export async function addPokemonDataToDexAndValidateAchievements(pokemon: Player
* @param scene * @param scene
* @param invalidSelectionKey * @param invalidSelectionKey
*/ */
export function isPokemonValidForEncounterOptionSelection(pokemon: Pokemon, invalidSelectionKey: string): string | null { export function isPokemonValidForEncounterOptionSelection(
pokemon: Pokemon,
invalidSelectionKey: string,
): string | null {
if (!pokemon.isAllowedInChallenge()) { if (!pokemon.isAllowedInChallenge()) {
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null; return (
i18next.t("partyUiHandler:cantBeUsed", {
pokemonName: pokemon.getNameToRender(),
}) ?? null
);
} }
if (!pokemon.isAllowedInBattle()) { if (!pokemon.isAllowedInBattle()) {
return getEncounterText(invalidSelectionKey) ?? null; return getEncounterText(invalidSelectionKey) ?? null;

View File

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

View File

@ -5,11 +5,17 @@ import { UiTheme } from "#enums/ui-theme";
import i18next from "i18next"; import i18next from "i18next";
import { Stat, EFFECTIVE_STATS, getShortenedStatKey } from "#enums/stat"; 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]); let ret = Utils.toReadableString(Nature[nature]);
//Translating nature //Translating nature
if (i18next.exists("nature:" + ret)) { if (i18next.exists(`nature:${ret}`)) {
ret = i18next.t("nature:" + ret as any); ret = i18next.t(`nature:${ret}` as any);
} }
if (includeStatEffects) { if (includeStatEffects) {
let increasedStat: Stat | null = null; 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 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) { 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)}`; 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 { } else {

View File

@ -95,16 +95,31 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number {
const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr); const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr);
const catchingCharmMultiplier = new NumberHolder(1); const catchingCharmMultiplier = new NumberHolder(1);
globalScene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier); globalScene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier);
const dexMultiplier = globalScene.gameMode.isDaily || dexCount > 800 ? 2.5 const dexMultiplier =
: dexCount > 600 ? 2 globalScene.gameMode.isDaily || dexCount > 800
: dexCount > 400 ? 1.5 ? 2.5
: dexCount > 200 ? 1 : dexCount > 600
: dexCount > 100 ? 0.5 ? 2
: dexCount > 400
? 1.5
: dexCount > 200
? 1
: dexCount > 100
? 0.5
: 0; : 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 bouncePower = 1;
let bounceYOffset = y1; let bounceYOffset = y1;
let bounceY = y2; let bounceY = y2;
@ -134,12 +149,12 @@ export function doPokeballBounceAnim(pokeball: Phaser.GameObjects.Sprite, y1: nu
y: bounceY, y: bounceY,
duration: bouncePower * baseBounceDuration, duration: bouncePower * baseBounceDuration,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
onComplete: () => doBounce() onComplete: () => doBounce(),
}); });
} else if (callback) { } else if (callback) {
callback(); callback();
} }
} },
}); });
}; };
@ -165,12 +180,12 @@ export function doPokeballBounceAnim(pokeball: Phaser.GameObjects.Sprite, y1: nu
x: x0, x: x0,
duration: 60, duration: 60,
ease: "Linear", ease: "Linear",
onComplete: () => globalScene.time.delayedCall(500, doBounce) onComplete: () => globalScene.time.delayedCall(500, doBounce),
}); });
} }
} },
}); });
} },
}); });
}; };

View File

@ -132,7 +132,7 @@ export enum FormChangeItem {
DRAGON_MEMORY, DRAGON_MEMORY,
DARK_MEMORY, DARK_MEMORY,
FAIRY_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; export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
@ -146,7 +146,14 @@ export class SpeciesFormChange {
public quiet: boolean; public quiet: boolean;
public readonly conditions: SpeciesFormChangeCondition[]; 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.speciesId = speciesId;
this.preFormKey = preFormKey; this.preFormKey = preFormKey;
this.formKey = evoFormKey; this.formKey = evoFormKey;
@ -212,9 +219,9 @@ export class SpeciesFormChangeCondition {
} }
export abstract class SpeciesFormChangeTrigger { export abstract class SpeciesFormChangeTrigger {
public description: string = ""; public description = "";
canChange(pokemon: Pokemon): boolean { canChange(_pokemon: Pokemon): boolean {
return true; return true;
} }
@ -223,20 +230,22 @@ export abstract class SpeciesFormChangeTrigger {
} }
} }
export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger { export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {}
}
export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger { export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger {
public description: string = i18next.t("pokemonEvolutions:Forms.ability"); public description: string = i18next.t("pokemonEvolutions:Forms.ability");
} }
export class SpeciesFormChangeCompoundTrigger { export class SpeciesFormChangeCompoundTrigger {
public description: string = ""; public description = "";
public triggers: SpeciesFormChangeTrigger[]; public triggers: SpeciesFormChangeTrigger[];
constructor(...triggers: SpeciesFormChangeTrigger[]) { constructor(...triggers: SpeciesFormChangeTrigger[]) {
this.triggers = triggers; 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 { canChange(pokemon: Pokemon): boolean {
@ -258,17 +267,27 @@ export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger {
public item: FormChangeItem; public item: FormChangeItem;
public active: boolean; public active: boolean;
constructor(item: FormChangeItem, active: boolean = true) { constructor(item: FormChangeItem, active = true) {
super(); super();
this.item = item; this.item = item;
this.active = active; this.active = active;
this.description = this.active ? this.description = this.active
i18next.t("pokemonEvolutions:Forms.item", { item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`) }) : ? i18next.t("pokemonEvolutions:Forms.item", {
i18next.t("pokemonEvolutions:Forms.deactivateItem", { item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.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 { 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,
);
} }
} }
@ -281,7 +300,7 @@ export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger
this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay"); this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay");
} }
canChange(pokemon: Pokemon): boolean { canChange(_pokemon: Pokemon): boolean {
return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1; return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1;
} }
} }
@ -289,10 +308,12 @@ export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger
export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger { export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger {
public active: boolean; public active: boolean;
constructor(active: boolean = false) { constructor(active = false) {
super(); super();
this.active = active; 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 { canChange(pokemon: Pokemon): boolean {
@ -304,7 +325,7 @@ export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigg
public statusEffects: StatusEffect[]; public statusEffects: StatusEffect[];
public invert: boolean; public invert: boolean;
constructor(statusEffects: StatusEffect | StatusEffect[], invert: boolean = false) { constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
super(); super();
if (!Array.isArray(statusEffects)) { if (!Array.isArray(statusEffects)) {
statusEffects = [statusEffects]; statusEffects = [statusEffects];
@ -315,7 +336,7 @@ export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigg
} }
canChange(pokemon: Pokemon): boolean { 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;
} }
} }
@ -323,17 +344,26 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge
public move: Moves; public move: Moves;
public known: boolean; public known: boolean;
constructor(move: Moves, known: boolean = true) { constructor(move: Moves, known = true) {
super(); super();
this.move = move; this.move = move;
this.known = known; 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; const moveKey = Moves[this.move]
this.description = known ? i18next.t("pokemonEvolutions:Forms.moveLearned", { move: i18next.t(`move:${moveKey}.name`) }) : .split("_")
i18next.t("pokemonEvolutions:Forms.moveForgotten", { move: i18next.t(`move:${moveKey}.name`) }); .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 { 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;
} }
} }
@ -341,7 +371,7 @@ export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrig
public movePredicate: (m: Moves) => boolean; public movePredicate: (m: Moves) => boolean;
public used: boolean; public used: boolean;
constructor(move: Moves | ((m: Moves) => boolean), used: boolean = true) { constructor(move: Moves | ((m: Moves) => boolean), used = true) {
super(); super();
this.movePredicate = typeof move === "function" ? move : (m: Moves) => m === move; this.movePredicate = typeof move === "function" ? move : (m: Moves) => m === move;
this.used = used; this.used = used;
@ -361,7 +391,9 @@ export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigg
description = i18next.t("pokemonEvolutions:Forms.postMove"); description = i18next.t("pokemonEvolutions:Forms.postMove");
canChange(pokemon: Pokemon): boolean { 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
);
} }
} }
@ -369,7 +401,7 @@ export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMove
override canChange(pokemon: Pokemon): boolean { override canChange(pokemon: Pokemon): boolean {
if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) { if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
return false; return false;
} else { }
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song // Meloetta will not transform if it has the ability Sheer Force when using Relic Song
if (pokemon.hasAbility(Abilities.SHEER_FORCE)) { if (pokemon.hasAbility(Abilities.SHEER_FORCE)) {
return false; return false;
@ -377,7 +409,6 @@ export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMove
return super.canChange(pokemon); return super.canChange(pokemon);
} }
} }
}
export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger { export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
private formKey: string; private formKey: string;
@ -389,7 +420,11 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
} }
canChange(pokemon: Pokemon): boolean { 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
);
} }
} }
@ -440,7 +475,12 @@ export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed(); const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed; 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)
);
} }
} }
@ -491,16 +531,27 @@ export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: Specie
const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1; const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1;
const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey; const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey;
if (isMega) { if (isMega) {
return i18next.t("battlePokemonForm:megaChange", { preName, pokemonName: pokemon.name }); return i18next.t("battlePokemonForm:megaChange", {
preName,
pokemonName: pokemon.name,
});
} }
if (isGmax) { if (isGmax) {
return i18next.t("battlePokemonForm:gigantamaxChange", { preName, pokemonName: pokemon.name }); return i18next.t("battlePokemonForm:gigantamaxChange", {
preName,
pokemonName: pokemon.name,
});
} }
if (isEmax) { if (isEmax) {
return i18next.t("battlePokemonForm:eternamaxChange", { preName, pokemonName: pokemon.name }); return i18next.t("battlePokemonForm:eternamaxChange", {
preName,
pokemonName: pokemon.name,
});
} }
if (isRevert) { if (isRevert) {
return i18next.t("battlePokemonForm:revertChange", { pokemonName: getPokemonNameWithAffix(pokemon) }); return i18next.t("battlePokemonForm:revertChange", {
pokemonName: getPokemonNameWithAffix(pokemon),
});
} }
if (pokemon.getAbility().id === Abilities.DISGUISE) { if (pokemon.getAbility().id === Abilities.DISGUISE) {
return i18next.t("battlePokemonForm:disguiseChange"); return i18next.t("battlePokemonForm:disguiseChange");
@ -515,13 +566,14 @@ export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: Specie
* @returns A {@linkcode SpeciesFormChangeCondition} checking if that species is registered as caught * @returns A {@linkcode SpeciesFormChangeCondition} checking if that species is registered as caught
*/ */
function getSpeciesDependentFormChangeCondition(species: Species): SpeciesFormChangeCondition { 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 { interface PokemonFormChanges {
[key: string]: SpeciesFormChange[] [key: string]: SpeciesFormChange[];
} }
// biome-ignore format: manually formatted
export const pokemonFormChanges: PokemonFormChanges = { export const pokemonFormChanges: PokemonFormChanges = {
[Species.VENUSAUR]: [ [Species.VENUSAUR]: [
new SpeciesFormChange(Species.VENUSAUR, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.VENUSAURITE)), new SpeciesFormChange(Species.VENUSAUR, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.VENUSAURITE)),
@ -994,16 +1046,22 @@ export const pokemonFormChanges: PokemonFormChanges = {
export function initPokemonForms() { export function initPokemonForms() {
const formChangeKeys = Object.keys(pokemonFormChanges); const formChangeKeys = Object.keys(pokemonFormChanges);
formChangeKeys.forEach(pk => { for (const pk of formChangeKeys) {
const formChanges = pokemonFormChanges[pk]; const formChanges = pokemonFormChanges[pk];
const newFormChanges: SpeciesFormChange[] = []; const newFormChanges: SpeciesFormChange[] = [];
for (const fc of formChanges) { for (const fc of formChanges) {
const itemTrigger = fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger; const itemTrigger = fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger;
if (itemTrigger && !formChanges.find(c => fc.formKey === c.preFormKey && fc.preFormKey === c.formKey)) { 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); formChanges.push(...newFormChanges);
});
} }
}

View File

@ -13,10 +13,18 @@ import { uncatchableSpecies } from "#app/data/balance/biomes";
import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { GrowthRate } from "#app/data/exp"; import { GrowthRate } from "#app/data/exp";
import type { EvolutionLevel } from "#app/data/balance/pokemon-evolutions"; import type { EvolutionLevel } from "#app/data/balance/pokemon-evolutions";
import { SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import {
SpeciesWildEvolutionDelay,
pokemonEvolutions,
pokemonPrevolutions,
} from "#app/data/balance/pokemon-evolutions";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves";
import { pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import {
pokemonFormLevelMoves,
pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves,
pokemonSpeciesLevelMoves,
} from "#app/data/balance/pokemon-level-moves";
import type { Stat } from "#enums/stat"; import type { Stat } from "#enums/stat";
import type { Variant, VariantSet } from "#app/data/variant"; import type { Variant, VariantSet } from "#app/data/variant";
import { variantData } from "#app/data/variant"; import { variantData } from "#app/data/variant";
@ -29,7 +37,7 @@ export enum Region {
ALOLA, ALOLA,
GALAR, GALAR,
HISUI, HISUI,
PALDEA PALDEA,
} }
// TODO: this is horrible and will need to be removed once a refactor/cleanup of forms is executed. // TODO: this is horrible and will need to be removed once a refactor/cleanup of forms is executed.
@ -60,7 +68,7 @@ export const normalForm: Species[] = [
Species.MARSHADOW, Species.MARSHADOW,
Species.CRAMORANT, Species.CRAMORANT,
Species.ZARUDE, Species.ZARUDE,
Species.CALYREX Species.CALYREX,
]; ];
/** /**
@ -81,7 +89,8 @@ export function getPokemonSpecies(species: Species | Species[]): PokemonSpecies
} }
export function getPokemonSpeciesForm(species: Species, formIndex: number): PokemonSpeciesForm { export function getPokemonSpeciesForm(species: Species, formIndex: number): PokemonSpeciesForm {
const retSpecies: PokemonSpecies = species >= 2000 const retSpecies: PokemonSpecies =
species >= 2000
? allSpecies.find(s => s.speciesId === species)! // TODO: is the bang correct? ? allSpecies.find(s => s.speciesId === species)! // TODO: is the bang correct?
: allSpecies[species - 1]; : allSpecies[species - 1];
if (formIndex < retSpecies.forms?.length) { if (formIndex < retSpecies.forms?.length) {
@ -95,7 +104,7 @@ export function getFusedSpeciesName(speciesAName: string, speciesBName: string):
const fragBPattern = /([a-z]{2}.*?[aeiou(?:y$)\-\'])(.*?)$/i; const fragBPattern = /([a-z]{2}.*?[aeiou(?:y$)\-\'])(.*?)$/i;
const [speciesAPrefixMatch, speciesBPrefixMatch] = [speciesAName, speciesBName].map(n => /^(?:[^ ]+) /.exec(n)); const [speciesAPrefixMatch, speciesBPrefixMatch] = [speciesAName, speciesBName].map(n => /^(?:[^ ]+) /.exec(n));
const [ speciesAPrefix, speciesBPrefix ] = [ speciesAPrefixMatch, speciesBPrefixMatch ].map(m => m ? m[0] : ""); const [speciesAPrefix, speciesBPrefix] = [speciesAPrefixMatch, speciesBPrefixMatch].map(m => (m ? m[0] : ""));
if (speciesAPrefix) { if (speciesAPrefix) {
speciesAName = speciesAName.slice(speciesAPrefix.length); speciesAName = speciesAName.slice(speciesAPrefix.length);
@ -105,7 +114,7 @@ export function getFusedSpeciesName(speciesAName: string, speciesBName: string):
} }
const [speciesASuffixMatch, speciesBSuffixMatch] = [speciesAName, speciesBName].map(n => / (?:[^ ]+)$/.exec(n)); const [speciesASuffixMatch, speciesBSuffixMatch] = [speciesAName, speciesBName].map(n => / (?:[^ ]+)$/.exec(n));
const [ speciesASuffix, speciesBSuffix ] = [ speciesASuffixMatch, speciesBSuffixMatch ].map(m => m ? m[0] : ""); const [speciesASuffix, speciesBSuffix] = [speciesASuffixMatch, speciesBSuffixMatch].map(m => (m ? m[0] : ""));
if (speciesASuffix) { if (speciesASuffix) {
speciesAName = speciesAName.slice(0, -speciesASuffix.length); speciesAName = speciesAName.slice(0, -speciesASuffix.length);
@ -123,9 +132,7 @@ export function getFusedSpeciesName(speciesAName: string, speciesBName: string):
let fragA: string; let fragA: string;
let fragB: string; let fragB: string;
fragA = splitNameA.length === 1 fragA = splitNameA.length === 1 ? (fragAMatch ? fragAMatch[1] : speciesAName) : splitNameA[splitNameA.length - 1];
? fragAMatch ? fragAMatch[1] : speciesAName
: splitNameA[splitNameA.length - 1];
if (splitNameB.length === 1) { if (splitNameB.length === 1) {
if (fragBMatch) { if (fragBMatch) {
@ -179,9 +186,26 @@ export abstract class PokemonSpeciesForm {
readonly genderDiffs: boolean; readonly genderDiffs: boolean;
readonly isStarterSelectable: boolean; readonly isStarterSelectable: boolean;
constructor(type1: PokemonType, type2: PokemonType | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities, constructor(
baseTotal: number, baseHp: number, baseAtk: number, baseDef: number, baseSpatk: number, baseSpdef: number, baseSpd: number, type1: PokemonType,
catchRate: number, baseFriendship: number, baseExp: number, genderDiffs: boolean, isStarterSelectable: boolean type2: PokemonType | null,
height: number,
weight: number,
ability1: Abilities,
ability2: Abilities,
abilityHidden: Abilities,
baseTotal: number,
baseHp: number,
baseAtk: number,
baseDef: number,
baseSpatk: number,
baseSpdef: number,
baseSpd: number,
catchRate: number,
baseFriendship: number,
baseExp: number,
genderDiffs: boolean,
isStarterSelectable: boolean,
) { ) {
this.type1 = type1; this.type1 = type1;
this.type2 = type2; this.type2 = type2;
@ -206,7 +230,7 @@ export abstract class PokemonSpeciesForm {
* @param forStarter boolean to get the nonbaby form of a starter * @param forStarter boolean to get the nonbaby form of a starter
* @returns The species * @returns The species
*/ */
getRootSpeciesId(forStarter: boolean = false): Species { getRootSpeciesId(forStarter = false): Species {
let ret = this.speciesId; let ret = this.speciesId;
while (pokemonPrevolutions.hasOwnProperty(ret) && (!forStarter || !speciesStarterCosts.hasOwnProperty(ret))) { while (pokemonPrevolutions.hasOwnProperty(ret) && (!forStarter || !speciesStarterCosts.hasOwnProperty(ret))) {
ret = pokemonPrevolutions[ret]; ret = pokemonPrevolutions[ret];
@ -269,23 +293,29 @@ export abstract class PokemonSpeciesForm {
formIndex = this.formIndex; formIndex = this.formIndex;
} }
let starterSpeciesId = this.speciesId; let starterSpeciesId = this.speciesId;
while (!(starterSpeciesId in starterPassiveAbilities) || !(formIndex in starterPassiveAbilities[starterSpeciesId])) { while (
!(starterSpeciesId in starterPassiveAbilities) ||
!(formIndex in starterPassiveAbilities[starterSpeciesId])
) {
if (pokemonPrevolutions.hasOwnProperty(starterSpeciesId)) { if (pokemonPrevolutions.hasOwnProperty(starterSpeciesId)) {
starterSpeciesId = pokemonPrevolutions[starterSpeciesId]; starterSpeciesId = pokemonPrevolutions[starterSpeciesId];
} else { // If we've reached the base species and still haven't found a matching ability, use form 0 if possible } else {
// If we've reached the base species and still haven't found a matching ability, use form 0 if possible
if (0 in starterPassiveAbilities[starterSpeciesId]) { if (0 in starterPassiveAbilities[starterSpeciesId]) {
return starterPassiveAbilities[starterSpeciesId][0]; return starterPassiveAbilities[starterSpeciesId][0];
} else { }
console.log("No passive ability found for %s, using run away", this.speciesId); console.log("No passive ability found for %s, using run away", this.speciesId);
return Abilities.RUN_AWAY; return Abilities.RUN_AWAY;
} }
} }
}
return starterPassiveAbilities[starterSpeciesId][formIndex]; return starterPassiveAbilities[starterSpeciesId][formIndex];
} }
getLevelMoves(): LevelMoves { getLevelMoves(): LevelMoves {
if (pokemonSpeciesFormLevelMoves.hasOwnProperty(this.speciesId) && pokemonSpeciesFormLevelMoves[this.speciesId].hasOwnProperty(this.formIndex)) { if (
pokemonSpeciesFormLevelMoves.hasOwnProperty(this.speciesId) &&
pokemonSpeciesFormLevelMoves[this.speciesId].hasOwnProperty(this.formIndex)
) {
return pokemonSpeciesFormLevelMoves[this.speciesId][this.formIndex].slice(0); return pokemonSpeciesFormLevelMoves[this.speciesId][this.formIndex].slice(0);
} }
return pokemonSpeciesLevelMoves[this.speciesId].slice(0); return pokemonSpeciesLevelMoves[this.speciesId].slice(0);
@ -296,7 +326,7 @@ export abstract class PokemonSpeciesForm {
} }
isObtainable(): boolean { isObtainable(): boolean {
return (this.generation <= 9 || pokemonPrevolutions.hasOwnProperty(this.speciesId)); return this.generation <= 9 || pokemonPrevolutions.hasOwnProperty(this.speciesId);
} }
isCatchable(): boolean { isCatchable(): boolean {
@ -357,18 +387,19 @@ export abstract class PokemonSpeciesForm {
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`; return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
} }
getSpriteId(female: boolean, formIndex?: number, shiny?: boolean, variant: number = 0, back?: boolean): string { getSpriteId(female: boolean, formIndex?: number, shiny?: boolean, variant = 0, back?: boolean): string {
if (formIndex === undefined || this instanceof PokemonForm) { if (formIndex === undefined || this instanceof PokemonForm) {
formIndex = this.formIndex; formIndex = this.formIndex;
} }
const formSpriteKey = this.getFormSpriteKey(formIndex); const formSpriteKey = this.getFormSpriteKey(formIndex);
const showGenderDiffs = this.genderDiffs && female && ![ SpeciesFormKey.MEGA, SpeciesFormKey.GIGANTAMAX ].find(k => formSpriteKey === k); const showGenderDiffs =
this.genderDiffs && female && ![SpeciesFormKey.MEGA, SpeciesFormKey.GIGANTAMAX].find(k => formSpriteKey === k);
const baseSpriteKey = `${showGenderDiffs ? "female__" : ""}${this.speciesId}${formSpriteKey ? `-${formSpriteKey}` : ""}`; const baseSpriteKey = `${showGenderDiffs ? "female__" : ""}${this.speciesId}${formSpriteKey ? `-${formSpriteKey}` : ""}`;
let config = variantData; let config = variantData;
`${back ? "back__" : ""}${baseSpriteKey}`.split("__").map(p => config ? config = config[p] : null); `${back ? "back__" : ""}${baseSpriteKey}`.split("__").map(p => (config ? (config = config[p]) : null));
const variantSet = config as VariantSet; const variantSet = config as VariantSet;
return `${back ? "back__" : ""}${shiny && (!variantSet || (!variant && !variantSet[variant || 0])) ? "shiny__" : ""}${baseSpriteKey}${shiny && variantSet && variantSet[variant] === 2 ? `_${variant + 1}` : ""}`; return `${back ? "back__" : ""}${shiny && (!variantSet || (!variant && !variantSet[variant || 0])) ? "shiny__" : ""}${baseSpriteKey}${shiny && variantSet && variantSet[variant] === 2 ? `_${variant + 1}` : ""}`;
@ -380,7 +411,6 @@ export abstract class PokemonSpeciesForm {
abstract getFormSpriteKey(formIndex?: number): string; abstract getFormSpriteKey(formIndex?: number): string;
/** /**
* Variant Data key/index is either species id or species id followed by -formkey * Variant Data key/index is either species id or species id followed by -formkey
* @param formIndex optional form index for pokemon with different forms * @param formIndex optional form index for pokemon with different forms
@ -401,7 +431,8 @@ export abstract class PokemonSpeciesForm {
getIconAtlasKey(formIndex?: number, shiny?: boolean, variant?: number): string { getIconAtlasKey(formIndex?: number, shiny?: boolean, variant?: number): string {
const variantDataIndex = this.getVariantDataIndex(formIndex); const variantDataIndex = this.getVariantDataIndex(formIndex);
const isVariant = shiny && variantData[variantDataIndex] && (variant !== undefined && variantData[variantDataIndex][variant]); const isVariant =
shiny && variantData[variantDataIndex] && variant !== undefined && variantData[variantDataIndex][variant];
return `pokemon_icons_${this.generation}${isVariant ? "v" : ""}`; return `pokemon_icons_${this.generation}${isVariant ? "v" : ""}`;
} }
@ -414,7 +445,8 @@ export abstract class PokemonSpeciesForm {
let ret = this.speciesId.toString(); let ret = this.speciesId.toString();
const isVariant = shiny && variantData[variantDataIndex] && (variant !== undefined && variantData[variantDataIndex][variant]); const isVariant =
shiny && variantData[variantDataIndex] && variant !== undefined && variantData[variantDataIndex][variant];
if (shiny && !isVariant) { if (shiny && !isVariant) {
ret += "s"; ret += "s";
@ -479,7 +511,9 @@ export abstract class PokemonSpeciesForm {
const forms = getPokemonSpecies(speciesId).forms; const forms = getPokemonSpecies(speciesId).forms;
if (forms.length) { if (forms.length) {
if (formIndex !== undefined && formIndex >= forms.length) { if (formIndex !== undefined && formIndex >= forms.length) {
console.warn(`Attempted accessing form with index ${formIndex} of species ${getPokemonSpecies(speciesId).getName()} with only ${forms.length || 0} forms`); console.warn(
`Attempted accessing form with index ${formIndex} of species ${getPokemonSpecies(speciesId).getName()} with only ${forms.length || 0} forms`,
);
formIndex = Math.min(formIndex, forms.length - 1); formIndex = Math.min(formIndex, forms.length - 1);
} }
const formKey = forms[formIndex || 0].formKey; const formKey = forms[formIndex || 0].formKey;
@ -532,11 +566,14 @@ export abstract class PokemonSpeciesForm {
for (const moveId of moveset) { for (const moveId of moveset) {
if (speciesEggMoves.hasOwnProperty(rootSpeciesId)) { if (speciesEggMoves.hasOwnProperty(rootSpeciesId)) {
const eggMoveIndex = speciesEggMoves[rootSpeciesId].findIndex(m => m === moveId); const eggMoveIndex = speciesEggMoves[rootSpeciesId].findIndex(m => m === moveId);
if (eggMoveIndex > -1 && (eggMoves & (1 << eggMoveIndex))) { if (eggMoveIndex > -1 && eggMoves & (1 << eggMoveIndex)) {
continue; continue;
} }
} }
if (pokemonFormLevelMoves.hasOwnProperty(this.speciesId) && pokemonFormLevelMoves[this.speciesId].hasOwnProperty(this.formIndex)) { if (
pokemonFormLevelMoves.hasOwnProperty(this.speciesId) &&
pokemonFormLevelMoves[this.speciesId].hasOwnProperty(this.formIndex)
) {
if (!pokemonFormLevelMoves[this.speciesId][this.formIndex].find(lm => lm[0] <= 5 && lm[1] === moveId)) { if (!pokemonFormLevelMoves[this.speciesId][this.formIndex].find(lm => lm[0] <= 5 && lm[1] === moveId)) {
return false; return false;
} }
@ -548,7 +585,14 @@ export abstract class PokemonSpeciesForm {
return true; return true;
} }
loadAssets(female: boolean, formIndex?: number, shiny?: boolean, variant?: Variant, startLoad?: boolean, back?: boolean): Promise<void> { loadAssets(
female: boolean,
formIndex?: number,
shiny?: boolean,
variant?: Variant,
startLoad?: boolean,
back?: boolean,
): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back); const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back);
globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back)); globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back));
@ -557,19 +601,26 @@ export abstract class PokemonSpeciesForm {
const originalWarn = console.warn; const originalWarn = console.warn;
// Ignore warnings for missing frames, because there will be a lot // Ignore warnings for missing frames, because there will be a lot
console.warn = () => {}; console.warn = () => {};
const frameNames = globalScene.anims.generateFrameNames(spriteKey, { zeroPad: 4, suffix: ".png", start: 1, end: 400 }); const frameNames = globalScene.anims.generateFrameNames(spriteKey, {
zeroPad: 4,
suffix: ".png",
start: 1,
end: 400,
});
console.warn = originalWarn; console.warn = originalWarn;
if (!(globalScene.anims.exists(spriteKey))) { if (!globalScene.anims.exists(spriteKey)) {
globalScene.anims.create({ globalScene.anims.create({
key: this.getSpriteKey(female, formIndex, shiny, variant, back), key: this.getSpriteKey(female, formIndex, shiny, variant, back),
frames: frameNames, frames: frameNames,
frameRate: 10, frameRate: 10,
repeat: -1 repeat: -1,
}); });
} else { } else {
globalScene.anims.get(spriteKey).frameRate = 10; globalScene.anims.get(spriteKey).frameRate = 10;
} }
const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant, back).replace("variant/", "").replace(/_[1-3]$/, ""); const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant, back)
.replace("variant/", "")
.replace(/_[1-3]$/, "");
globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve()); globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve());
}); });
if (startLoad) { if (startLoad) {
@ -630,7 +681,14 @@ export abstract class PokemonSpeciesForm {
if (!total) { if (!total) {
continue; continue;
} }
pixelColors.push(argbFromRgba({ r: pixelData[i], g: pixelData[i + 1], b: pixelData[i + 2], a: pixelData[i + 3] })); pixelColors.push(
argbFromRgba({
r: pixelData[i],
g: pixelData[i + 1],
b: pixelData[i + 2],
a: pixelData[i + 3],
}),
);
} }
} }
@ -639,9 +697,13 @@ export abstract class PokemonSpeciesForm {
const originalRandom = Math.random; const originalRandom = Math.random;
Math.random = () => Phaser.Math.RND.realInRange(0, 1); Math.random = () => Phaser.Math.RND.realInRange(0, 1);
globalScene.executeWithSeedOffset(() => { globalScene.executeWithSeedOffset(
() => {
paletteColors = QuantizerCelebi.quantize(pixelColors, 2); paletteColors = QuantizerCelebi.quantize(pixelColors, 2);
}, 0, "This result should not vary"); },
0,
"This result should not vary",
);
Math.random = originalRandom; Math.random = originalRandom;
@ -661,14 +723,57 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
readonly canChangeForm: boolean; readonly canChangeForm: boolean;
readonly forms: PokemonForm[]; readonly forms: PokemonForm[];
constructor(id: Species, generation: number, subLegendary: boolean, legendary: boolean, mythical: boolean, species: string, constructor(
type1: PokemonType, type2: PokemonType | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities, id: Species,
baseTotal: number, baseHp: number, baseAtk: number, baseDef: number, baseSpatk: number, baseSpdef: number, baseSpd: number, generation: number,
catchRate: number, baseFriendship: number, baseExp: number, growthRate: GrowthRate, malePercent: number | null, subLegendary: boolean,
genderDiffs: boolean, canChangeForm?: boolean, ...forms: PokemonForm[] legendary: boolean,
mythical: boolean,
species: string,
type1: PokemonType,
type2: PokemonType | null,
height: number,
weight: number,
ability1: Abilities,
ability2: Abilities,
abilityHidden: Abilities,
baseTotal: number,
baseHp: number,
baseAtk: number,
baseDef: number,
baseSpatk: number,
baseSpdef: number,
baseSpd: number,
catchRate: number,
baseFriendship: number,
baseExp: number,
growthRate: GrowthRate,
malePercent: number | null,
genderDiffs: boolean,
canChangeForm?: boolean,
...forms: PokemonForm[]
) { ) {
super(type1, type2, height, weight, ability1, ability2, abilityHidden, baseTotal, baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd, super(
catchRate, baseFriendship, baseExp, genderDiffs, false); type1,
type2,
height,
weight,
ability1,
ability2,
abilityHidden,
baseTotal,
baseHp,
baseAtk,
baseDef,
baseSpatk,
baseSpdef,
baseSpd,
catchRate,
baseFriendship,
baseExp,
genderDiffs,
false,
);
this.speciesId = id; this.speciesId = id;
this.formIndex = 0; this.formIndex = 0;
this.generation = generation; this.generation = generation;
@ -712,7 +817,9 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
} }
if (key) { if (key) {
return i18next.t(`battlePokemonForm:${key}`, { pokemonName: this.name }); return i18next.t(`battlePokemonForm:${key}`, {
pokemonName: this.name,
});
} }
} }
return this.name; return this.name;
@ -725,9 +832,9 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
getExpandedSpeciesName(): string { getExpandedSpeciesName(): string {
if (this.speciesId < 2000) { if (this.speciesId < 2000) {
return this.name; // Other special cases could be put here too return this.name; // Other special cases could be put here too
} else { // Everything beyond this point essentially follows the pattern of FORMNAME_SPECIES
return i18next.t(`pokemonForm:appendForm.${Species[this.speciesId].split("_")[0]}`, { pokemonName: this.name });
} }
// Everything beyond this point essentially follows the pattern of FORMNAME_SPECIES
return i18next.t(`pokemonForm:appendForm.${Species[this.speciesId].split("_")[0]}`, { pokemonName: this.name });
} }
/** /**
@ -736,18 +843,36 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
* @param append Whether to append the species name to the end (defaults to false) * @param append Whether to append the species name to the end (defaults to false)
* @returns the pokemon-form locale key for the single form name ("Alolan Form", "Eternal Flower" etc) * @returns the pokemon-form locale key for the single form name ("Alolan Form", "Eternal Flower" etc)
*/ */
getFormNameToDisplay(formIndex: number = 0, append: boolean = false): string { getFormNameToDisplay(formIndex = 0, append = false): string {
const formKey = this.forms?.[formIndex!]?.formKey; const formKey = this.forms?.[formIndex!]?.formKey;
const formText = Utils.capitalizeString(formKey, "-", false, false) || ""; const formText = Utils.capitalizeString(formKey, "-", false, false) || "";
const speciesName = Utils.capitalizeString(Species[this.speciesId], "_", true, false); const speciesName = Utils.capitalizeString(Species[this.speciesId], "_", true, false);
let ret: string = ""; let ret = "";
const region = this.getRegion(); const region = this.getRegion();
if (this.speciesId === Species.ARCEUS) { if (this.speciesId === Species.ARCEUS) {
ret = i18next.t(`pokemonInfo:Type.${formText?.toUpperCase()}`); ret = i18next.t(`pokemonInfo:Type.${formText?.toUpperCase()}`);
} else if ([ SpeciesFormKey.MEGA, SpeciesFormKey.MEGA_X, SpeciesFormKey.MEGA_Y, SpeciesFormKey.PRIMAL, SpeciesFormKey.GIGANTAMAX, SpeciesFormKey.GIGANTAMAX_RAPID, SpeciesFormKey.GIGANTAMAX_SINGLE, SpeciesFormKey.ETERNAMAX ].includes(formKey as SpeciesFormKey)) { } else if (
return append ? i18next.t(`battlePokemonForm:${formKey}`, { pokemonName: this.name }) : i18next.t(`pokemonForm:battleForm.${formKey}`); [
} else if (region === Region.NORMAL || (this.speciesId === Species.GALAR_DARMANITAN && formIndex > 0) || this.speciesId === Species.PALDEA_TAUROS) { // More special cases can be added here SpeciesFormKey.MEGA,
SpeciesFormKey.MEGA_X,
SpeciesFormKey.MEGA_Y,
SpeciesFormKey.PRIMAL,
SpeciesFormKey.GIGANTAMAX,
SpeciesFormKey.GIGANTAMAX_RAPID,
SpeciesFormKey.GIGANTAMAX_SINGLE,
SpeciesFormKey.ETERNAMAX,
].includes(formKey as SpeciesFormKey)
) {
return append
? i18next.t(`battlePokemonForm:${formKey}`, { pokemonName: this.name })
: i18next.t(`pokemonForm:battleForm.${formKey}`);
} else if (
region === Region.NORMAL ||
(this.speciesId === Species.GALAR_DARMANITAN && formIndex > 0) ||
this.speciesId === Species.PALDEA_TAUROS
) {
// More special cases can be added here
const i18key = `pokemonForm:${speciesName}${formText}`; const i18key = `pokemonForm:${speciesName}${formText}`;
if (i18next.exists(i18key)) { if (i18next.exists(i18key)) {
ret = i18next.t(i18key); ret = i18next.t(i18key);
@ -756,16 +881,25 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
const i18RootKey = `pokemonForm:${rootSpeciesName}${formText}`; const i18RootKey = `pokemonForm:${rootSpeciesName}${formText}`;
ret = i18next.exists(i18RootKey) ? i18next.t(i18RootKey) : formText; ret = i18next.exists(i18RootKey) ? i18next.t(i18RootKey) : formText;
} }
} else if (append) { // Everything beyond this has an expanded name } else if (append) {
// Everything beyond this has an expanded name
return this.getExpandedSpeciesName(); return this.getExpandedSpeciesName();
} else if (this.speciesId === Species.ETERNAL_FLOETTE) { // Not a real form, so the key is made up } else if (this.speciesId === Species.ETERNAL_FLOETTE) {
// Not a real form, so the key is made up
return i18next.t("pokemonForm:floetteEternalFlower"); return i18next.t("pokemonForm:floetteEternalFlower");
} else if (this.speciesId === Species.BLOODMOON_URSALUNA) { // Not a real form, so the key is made up } else if (this.speciesId === Species.BLOODMOON_URSALUNA) {
// Not a real form, so the key is made up
return i18next.t("pokemonForm:ursalunaBloodmoon"); return i18next.t("pokemonForm:ursalunaBloodmoon");
} else { // Only regional forms should be left at this point } else {
// Only regional forms should be left at this point
return i18next.t(`pokemonForm:regionalForm.${Region[region]}`); return i18next.t(`pokemonForm:regionalForm.${Region[region]}`);
} }
return append ? i18next.t("pokemonForm:appendForm.GENERIC", { pokemonName: this.name, formName: ret }) : ret; return append
? i18next.t("pokemonForm:appendForm.GENERIC", {
pokemonName: this.name,
formName: ret,
})
: ret;
} }
localize(): void { localize(): void {
@ -773,10 +907,20 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
} }
getWildSpeciesForLevel(level: number, allowEvolving: boolean, isBoss: boolean, gameMode: GameMode): Species { getWildSpeciesForLevel(level: number, allowEvolving: boolean, isBoss: boolean, gameMode: GameMode): Species {
return this.getSpeciesForLevel(level, allowEvolving, false, (isBoss ? PartyMemberStrength.WEAKER : PartyMemberStrength.AVERAGE) + (gameMode?.isEndless ? 1 : 0)); return this.getSpeciesForLevel(
level,
allowEvolving,
false,
(isBoss ? PartyMemberStrength.WEAKER : PartyMemberStrength.AVERAGE) + (gameMode?.isEndless ? 1 : 0),
);
} }
getTrainerSpeciesForLevel(level: number, allowEvolving: boolean = false, strength: PartyMemberStrength, currentWave: number = 0): Species { getTrainerSpeciesForLevel(
level: number,
allowEvolving = false,
strength: PartyMemberStrength,
currentWave = 0,
): Species {
return this.getSpeciesForLevel(level, allowEvolving, true, strength, currentWave); return this.getSpeciesForLevel(level, allowEvolving, true, strength, currentWave);
} }
@ -815,7 +959,13 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
} }
} }
getSpeciesForLevel(level: number, allowEvolving: boolean = false, forTrainer: boolean = false, strength: PartyMemberStrength = PartyMemberStrength.WEAKER, currentWave: number = 0): Species { getSpeciesForLevel(
level: number,
allowEvolving = false,
forTrainer = false,
strength: PartyMemberStrength = PartyMemberStrength.WEAKER,
currentWave = 0,
): Species {
const prevolutionLevels = this.getPrevolutionLevels(); const prevolutionLevels = this.getPrevolutionLevels();
if (prevolutionLevels.length) { if (prevolutionLevels.length) {
@ -827,10 +977,13 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
} }
} }
if ( // If evolutions shouldn't happen, add more cases here :) if (
!allowEvolving // If evolutions shouldn't happen, add more cases here :)
|| !pokemonEvolutions.hasOwnProperty(this.speciesId) !allowEvolving ||
|| globalScene.currentBattle?.waveIndex === 20 && globalScene.gameMode.isClassic && globalScene.currentBattle.trainer !pokemonEvolutions.hasOwnProperty(this.speciesId) ||
(globalScene.currentBattle?.waveIndex === 20 &&
globalScene.gameMode.isClassic &&
globalScene.currentBattle.trainer)
) { ) {
return this.speciesId; return this.speciesId;
} }
@ -864,20 +1017,32 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
const maxLevelDiff = this.getStrengthLevelDiff(strength); //The maximum distance from the evolution level tolerated for the mon to not evolve const maxLevelDiff = this.getStrengthLevelDiff(strength); //The maximum distance from the evolution level tolerated for the mon to not evolve
const minChance: number = 0.875 - 0.125 * strength; const minChance: number = 0.875 - 0.125 * strength;
evolutionChance = Math.min(minChance + easeInFunc(Math.min(level - ev.level, maxLevelDiff) / maxLevelDiff) * (1 - minChance), 1); evolutionChance = Math.min(
minChance + easeInFunc(Math.min(level - ev.level, maxLevelDiff) / maxLevelDiff) * (1 - minChance),
1,
);
} }
} else { } else {
const preferredMinLevel = Math.max((ev.level - 1) + (ev.wildDelay!) * this.getStrengthLevelDiff(strength), 1); // TODO: is the bang correct? const preferredMinLevel = Math.max(ev.level - 1 + ev.wildDelay! * this.getStrengthLevelDiff(strength), 1); // TODO: is the bang correct?
let evolutionLevel = Math.max(ev.level > 1 ? ev.level : Math.floor(preferredMinLevel / 2), 1); let evolutionLevel = Math.max(ev.level > 1 ? ev.level : Math.floor(preferredMinLevel / 2), 1);
if (ev.level <= 1 && pokemonPrevolutions.hasOwnProperty(this.speciesId)) { if (ev.level <= 1 && pokemonPrevolutions.hasOwnProperty(this.speciesId)) {
const prevolutionLevel = pokemonEvolutions[pokemonPrevolutions[this.speciesId]].find(ev => ev.speciesId === this.speciesId)!.level; // TODO: is the bang correct? const prevolutionLevel = pokemonEvolutions[pokemonPrevolutions[this.speciesId]].find(
ev => ev.speciesId === this.speciesId,
)!.level; // TODO: is the bang correct?
if (prevolutionLevel > 1) { if (prevolutionLevel > 1) {
evolutionLevel = prevolutionLevel; evolutionLevel = prevolutionLevel;
} }
} }
evolutionChance = Math.min(0.65 * easeInFunc(Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel) / preferredMinLevel) + 0.35 * easeOutFunc(Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel * 2.5) / (preferredMinLevel * 2.5)), 1); evolutionChance = Math.min(
0.65 * easeInFunc(Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel) / preferredMinLevel) +
0.35 *
easeOutFunc(
Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel * 2.5) / (preferredMinLevel * 2.5),
),
1,
);
} }
} }
@ -890,14 +1055,14 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
if (evolutionChance > 0) { if (evolutionChance > 0) {
if (isRegionalEvolution) { if (isRegionalEvolution) {
evolutionChance /= (evolutionSpecies.isRareRegional() ? 16 : 4); evolutionChance /= evolutionSpecies.isRareRegional() ? 16 : 4;
} }
totalWeight += evolutionChance; totalWeight += evolutionChance;
evolutionPool.set(totalWeight, ev.speciesId); evolutionPool.set(totalWeight, ev.speciesId);
if ((1 - evolutionChance) < noEvolutionChance) { if (1 - evolutionChance < noEvolutionChance) {
noEvolutionChance = 1 - evolutionChance; noEvolutionChance = 1 - evolutionChance;
} }
} }
@ -912,7 +1077,13 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
for (const weight of evolutionPool.keys()) { for (const weight of evolutionPool.keys()) {
if (randValue < weight) { if (randValue < weight) {
// TODO: this entire function is dumb and should be changed, adding a `!` here for now until then // TODO: this entire function is dumb and should be changed, adding a `!` here for now until then
return getPokemonSpecies(evolutionPool.get(weight)!).getSpeciesForLevel(level, true, forTrainer, strength, currentWave); return getPokemonSpecies(evolutionPool.get(weight)!).getSpeciesForLevel(
level,
true,
forTrainer,
strength,
currentWave,
);
} }
} }
@ -946,8 +1117,12 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
const allEvolvingPokemon = Object.keys(pokemonEvolutions); const allEvolvingPokemon = Object.keys(pokemonEvolutions);
for (const p of allEvolvingPokemon) { for (const p of allEvolvingPokemon) {
for (const e of pokemonEvolutions[p]) { for (const e of pokemonEvolutions[p]) {
if (e.speciesId === this.speciesId && (!this.forms.length || !e.evoFormKey || e.evoFormKey === this.forms[this.formIndex].formKey) && prevolutionLevels.every(pe => pe[0] !== parseInt(p))) { if (
const speciesId = parseInt(p) as Species; e.speciesId === this.speciesId &&
(!this.forms.length || !e.evoFormKey || e.evoFormKey === this.forms[this.formIndex].formKey) &&
prevolutionLevels.every(pe => pe[0] !== Number.parseInt(p))
) {
const speciesId = Number.parseInt(p) as Species;
const level = e.level; const level = e.level;
prevolutionLevels.push([speciesId, level]); prevolutionLevels.push([speciesId, level]);
const subPrevolutionLevels = getPokemonSpecies(speciesId).getPrevolutionLevels(); const subPrevolutionLevels = getPokemonSpecies(speciesId).getPrevolutionLevels();
@ -962,19 +1137,51 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
} }
// This could definitely be written better and more accurate to the getSpeciesForLevel logic, but it is only for generating movesets for evolved Pokemon // This could definitely be written better and more accurate to the getSpeciesForLevel logic, but it is only for generating movesets for evolved Pokemon
getSimulatedEvolutionChain(currentLevel: number, forTrainer: boolean = false, isBoss: boolean = false, player: boolean = false): EvolutionLevel[] { getSimulatedEvolutionChain(
currentLevel: number,
forTrainer = false,
isBoss = false,
player = false,
): EvolutionLevel[] {
const ret: EvolutionLevel[] = []; const ret: EvolutionLevel[] = [];
if (pokemonPrevolutions.hasOwnProperty(this.speciesId)) { if (pokemonPrevolutions.hasOwnProperty(this.speciesId)) {
const prevolutionLevels = this.getPrevolutionLevels().reverse(); const prevolutionLevels = this.getPrevolutionLevels().reverse();
const levelDiff = player ? 0 : forTrainer || isBoss ? forTrainer && isBoss ? 2.5 : 5 : 10; const levelDiff = player ? 0 : forTrainer || isBoss ? (forTrainer && isBoss ? 2.5 : 5) : 10;
ret.push([prevolutionLevels[0][0], 1]); ret.push([prevolutionLevels[0][0], 1]);
for (let l = 1; l < prevolutionLevels.length; l++) { for (let l = 1; l < prevolutionLevels.length; l++) {
const evolution = pokemonEvolutions[prevolutionLevels[l - 1][0]].find(e => e.speciesId === prevolutionLevels[l][0]); const evolution = pokemonEvolutions[prevolutionLevels[l - 1][0]].find(
ret.push([ prevolutionLevels[l][0], Math.min(Math.max((evolution?.level!) + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max((evolution?.wildDelay!), 0.5) * 5) - 1, 2, (evolution?.level!)), currentLevel - 1) ]); // TODO: are those bangs correct? e => e.speciesId === prevolutionLevels[l][0],
);
ret.push([
prevolutionLevels[l][0],
Math.min(
Math.max(
evolution?.level! +
Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution?.wildDelay!, 0.5) * 5) -
1,
2,
evolution?.level!,
),
currentLevel - 1,
),
]); // TODO: are those bangs correct?
} }
const lastPrevolutionLevel = ret[prevolutionLevels.length - 1][1]; const lastPrevolutionLevel = ret[prevolutionLevels.length - 1][1];
const evolution = pokemonEvolutions[prevolutionLevels[prevolutionLevels.length - 1][0]].find(e => e.speciesId === this.speciesId); const evolution = pokemonEvolutions[prevolutionLevels[prevolutionLevels.length - 1][0]].find(
ret.push([ this.speciesId, Math.min(Math.max(lastPrevolutionLevel + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max((evolution?.wildDelay!), 0.5) * 5), lastPrevolutionLevel + 1, (evolution?.level!)), currentLevel) ]); // TODO: are those bangs correct? e => e.speciesId === this.speciesId,
);
ret.push([
this.speciesId,
Math.min(
Math.max(
lastPrevolutionLevel +
Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution?.wildDelay!, 0.5) * 5),
lastPrevolutionLevel + 1,
evolution?.level!,
),
currentLevel,
),
]); // TODO: are those bangs correct?
} else { } else {
ret.push([this.speciesId, 1]); ret.push([this.speciesId, 1]);
} }
@ -990,19 +1197,17 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
const mythical = this.mythical; const mythical = this.mythical;
return species => { return species => {
return ( return (
subLegendary (subLegendary ||
|| legendary legendary ||
|| mythical mythical ||
|| ( (pokemonEvolutions.hasOwnProperty(species.speciesId) === hasEvolution &&
pokemonEvolutions.hasOwnProperty(species.speciesId) === hasEvolution pokemonPrevolutions.hasOwnProperty(species.speciesId) === hasPrevolution)) &&
&& pokemonPrevolutions.hasOwnProperty(species.speciesId) === hasPrevolution species.subLegendary === subLegendary &&
) species.legendary === legendary &&
) species.mythical === mythical &&
&& species.subLegendary === subLegendary (this.isTrainerForbidden() || !species.isTrainerForbidden()) &&
&& species.legendary === legendary species.speciesId !== Species.DITTO
&& species.mythical === mythical );
&& (this.isTrainerForbidden() || !species.isTrainerForbidden())
&& species.speciesId !== Species.DITTO;
}; };
} }
@ -1022,13 +1227,13 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
} }
getFormSpriteKey(formIndex?: number) { getFormSpriteKey(formIndex?: number) {
if (this.forms.length && (formIndex !== undefined && formIndex >= this.forms.length)) { if (this.forms.length && formIndex !== undefined && formIndex >= this.forms.length) {
console.warn(`Attempted accessing form with index ${formIndex} of species ${this.getName()} with only ${this.forms.length || 0} forms`); console.warn(
`Attempted accessing form with index ${formIndex} of species ${this.getName()} with only ${this.forms.length || 0} forms`,
);
formIndex = Math.min(formIndex, this.forms.length - 1); formIndex = Math.min(formIndex, this.forms.length - 1);
} }
return this.forms?.length return this.forms?.length ? this.forms[formIndex || 0].getFormSpriteKey() : "";
? this.forms[formIndex || 0].getFormSpriteKey()
: "";
} }
/** /**
@ -1037,7 +1242,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
* @returns {@linkcode bigint} Maximum unlocks, can be compared with {@linkcode DexEntry.caughtAttr}. * @returns {@linkcode bigint} Maximum unlocks, can be compared with {@linkcode DexEntry.caughtAttr}.
*/ */
getFullUnlocksData(): bigint { getFullUnlocksData(): bigint {
let caughtAttr: bigint = 0n; let caughtAttr = 0n;
caughtAttr += DexAttr.NON_SHINY; caughtAttr += DexAttr.NON_SHINY;
caughtAttr += DexAttr.SHINY; caughtAttr += DexAttr.SHINY;
if (this.malePercent !== null) { if (this.malePercent !== null) {
@ -1055,9 +1260,12 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
} }
// Summing successive bigints for each obtainable form // Summing successive bigints for each obtainable form
caughtAttr += this?.forms?.length > 1 ? caughtAttr +=
this.forms.map((f, index) => f.isUnobtainable ? 0n : 128n * 2n ** BigInt(index)).reduce((acc, val) => acc + val, 0n) : this?.forms?.length > 1
DexAttr.DEFAULT_FORM; ? this.forms
.map((f, index) => (f.isUnobtainable ? 0n : 128n * 2n ** BigInt(index)))
.reduce((acc, val) => acc + val, 0n)
: DexAttr.DEFAULT_FORM;
return caughtAttr; return caughtAttr;
} }
@ -1070,15 +1278,66 @@ export class PokemonForm extends PokemonSpeciesForm {
public isUnobtainable: boolean; public isUnobtainable: boolean;
// This is a collection of form keys that have in-run form changes, but should still be separately selectable from the start screen // This is a collection of form keys that have in-run form changes, but should still be separately selectable from the start screen
private starterSelectableKeys: string[] = [ "10", "50", "10-pc", "50-pc", "red", "orange", "yellow", "green", "blue", "indigo", "violet" ]; private starterSelectableKeys: string[] = [
"10",
"50",
"10-pc",
"50-pc",
"red",
"orange",
"yellow",
"green",
"blue",
"indigo",
"violet",
];
constructor(formName: string, formKey: string, type1: PokemonType, type2: PokemonType | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities, constructor(
baseTotal: number, baseHp: number, baseAtk: number, baseDef: number, baseSpatk: number, baseSpdef: number, baseSpd: number, formName: string,
catchRate: number, baseFriendship: number, baseExp: number, genderDiffs: boolean = false, formSpriteKey: string | null = null, isStarterSelectable: boolean = false, formKey: string,
isUnobtainable: boolean = false type1: PokemonType,
type2: PokemonType | null,
height: number,
weight: number,
ability1: Abilities,
ability2: Abilities,
abilityHidden: Abilities,
baseTotal: number,
baseHp: number,
baseAtk: number,
baseDef: number,
baseSpatk: number,
baseSpdef: number,
baseSpd: number,
catchRate: number,
baseFriendship: number,
baseExp: number,
genderDiffs = false,
formSpriteKey: string | null = null,
isStarterSelectable = false,
isUnobtainable = false,
) { ) {
super(type1, type2, height, weight, ability1, ability2, abilityHidden, baseTotal, baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd, super(
catchRate, baseFriendship, baseExp, genderDiffs, (isStarterSelectable || !formKey)); type1,
type2,
height,
weight,
ability1,
ability2,
abilityHidden,
baseTotal,
baseHp,
baseAtk,
baseDef,
baseSpatk,
baseSpdef,
baseSpd,
catchRate,
baseFriendship,
baseExp,
genderDiffs,
isStarterSelectable || !formKey,
);
this.formName = formName; this.formName = formName;
this.formKey = formKey; this.formKey = formKey;
this.formSpriteKey = formSpriteKey; this.formSpriteKey = formSpriteKey;
@ -1098,20 +1357,25 @@ export function getPokerusStarters(): PokemonSpecies[] {
const pokerusStarters: PokemonSpecies[] = []; const pokerusStarters: PokemonSpecies[] = [];
const date = new Date(); const date = new Date();
date.setUTCHours(0, 0, 0, 0); date.setUTCHours(0, 0, 0, 0);
globalScene.executeWithSeedOffset(() => { globalScene.executeWithSeedOffset(
() => {
while (pokerusStarters.length < POKERUS_STARTER_COUNT) { while (pokerusStarters.length < POKERUS_STARTER_COUNT) {
const randomSpeciesId = parseInt(Utils.randSeedItem(Object.keys(speciesStarterCosts)), 10); const randomSpeciesId = Number.parseInt(Utils.randSeedItem(Object.keys(speciesStarterCosts)), 10);
const species = getPokemonSpecies(randomSpeciesId); const species = getPokemonSpecies(randomSpeciesId);
if (!pokerusStarters.includes(species)) { if (!pokerusStarters.includes(species)) {
pokerusStarters.push(species); pokerusStarters.push(species);
} }
} }
}, 0, date.getTime().toString()); },
0,
date.getTime().toString(),
);
return pokerusStarters; return pokerusStarters;
} }
export const allSpecies: PokemonSpecies[] = []; export const allSpecies: PokemonSpecies[] = [];
// biome-ignore format: manually formatted
export function initSpecies() { export function initSpecies() {
allSpecies.push( allSpecies.push(
new PokemonSpecies(Species.BULBASAUR, 1, false, false, false, "Seed Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.7, 6.9, Abilities.OVERGROW, Abilities.NONE, Abilities.CHLOROPHYLL, 318, 45, 49, 49, 65, 65, 45, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.BULBASAUR, 1, false, false, false, "Seed Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.7, 6.9, Abilities.OVERGROW, Abilities.NONE, Abilities.CHLOROPHYLL, 318, 45, 49, 49, 65, 65, 45, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false),
@ -2887,23 +3151,3 @@ export function initSpecies() {
new PokemonSpecies(Species.BLOODMOON_URSALUNA, 9, true, false, false, "Peat Pokémon", PokemonType.GROUND, PokemonType.NORMAL, 2.7, 333, Abilities.MINDS_EYE, Abilities.NONE, Abilities.NONE, 555, 113, 70, 120, 135, 65, 52, 75, 50, 278, GrowthRate.MEDIUM_FAST, 50, false), //Marked as Sub-Legend, for casing purposes new PokemonSpecies(Species.BLOODMOON_URSALUNA, 9, true, false, false, "Peat Pokémon", PokemonType.GROUND, PokemonType.NORMAL, 2.7, 333, Abilities.MINDS_EYE, Abilities.NONE, Abilities.NONE, 555, 113, 70, 120, 135, 65, 52, 75, 50, 278, GrowthRate.MEDIUM_FAST, 50, false), //Marked as Sub-Legend, for casing purposes
); );
} }
// TODO: Remove
{
//setTimeout(() => {
/*for (let tc of Object.keys(trainerConfigs)) {
console.log(TrainerType[tc], !trainerConfigs[tc].speciesFilter ? 'all' : [...new Set(allSpecies.filter(s => s.generation <= 9).filter(trainerConfigs[tc].speciesFilter).map(s => {
while (pokemonPrevolutions.hasOwnProperty(s.speciesId))
s = getPokemonSpecies(pokemonPrevolutions[s.speciesId]);
return s;
}))].map(s => s.name));
}
const speciesFilter = (species: PokemonSpecies) => !species.legendary && !species.pseudoLegendary && !species.mythical && species.baseTotal >= 540;
console.log(!speciesFilter ? 'all' : [...new Set(allSpecies.filter(s => s.generation <= 9).filter(speciesFilter).map(s => {
while (pokemonPrevolutions.hasOwnProperty(s.speciesId))
s = getPokemonSpecies(pokemonPrevolutions[s.speciesId]);
return s;
}))].map(s => s.name));*/
//}, 1000);
}

View File

@ -185,13 +185,25 @@ const seasonalSplashMessages: Season[] = [
name: "Halloween", name: "Halloween",
start: "09-15", start: "09-15",
end: "10-31", 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", name: "XMAS",
start: "12-01", start: "12-01",
end: "12-26", 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", name: "New Year's",
@ -215,13 +227,13 @@ export function getSplashMessages(): string[] {
if (now >= startDate && now <= endDate) { if (now >= startDate && now <= endDate) {
console.log(`Adding ${messages.length} ${name} splash messages (weight: x${SEASONAL_WEIGHT_MULTIPLIER})`); 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); const weightedMessage = Array(SEASONAL_WEIGHT_MULTIPLIER).fill(message);
splashMessages.push(...weightedMessage); 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 { export class Status {
public effect: StatusEffect; public effect: StatusEffect;
/** Toxic damage is `1/16 max HP * toxicTurnCount` */ /** Toxic damage is `1/16 max HP * toxicTurnCount` */
public toxicTurnCount: number = 0; public toxicTurnCount = 0;
public sleepTurnsRemaining?: number; public sleepTurnsRemaining?: number;
constructor(effect: StatusEffect, toxicTurnCount: number = 0, sleepTurnsRemaining?: number) { constructor(effect: StatusEffect, toxicTurnCount = 0, sleepTurnsRemaining?: number) {
this.effect = effect; this.effect = effect;
this.toxicTurnCount = toxicTurnCount; this.toxicTurnCount = toxicTurnCount;
this.sleepTurnsRemaining = sleepTurnsRemaining; this.sleepTurnsRemaining = sleepTurnsRemaining;
@ -23,7 +23,9 @@ export class Status {
} }
isPostTurn(): boolean { 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) { if (statusEffect === StatusEffect.NONE) {
return ""; return "";
} }
@ -56,7 +62,10 @@ export function getStatusEffectObtainText(statusEffect: StatusEffect | undefined
return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix }); return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix });
} }
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.obtainSource` as ParseKeys; 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 { export function getStatusEffectActivationText(statusEffect: StatusEffect, pokemonNameWithAffix: string): string {
@ -142,7 +151,6 @@ export function getRandomStatus(statusA: Status | null, statusB: Status | null):
return statusA; return statusA;
} }
return randIntRange(0, 2) ? statusA : statusB; return randIntRange(0, 2) ? statusA : statusB;
} }
@ -157,7 +165,7 @@ export function getNonVolatileStatusEffects():Array<StatusEffect> {
StatusEffect.PARALYSIS, StatusEffect.PARALYSIS,
StatusEffect.SLEEP, StatusEffect.SLEEP,
StatusEffect.FREEZE, StatusEffect.FREEZE,
StatusEffect.BURN StatusEffect.BURN,
]; ];
} }

View File

@ -10,7 +10,7 @@ export enum TerrainType {
MISTY, MISTY,
ELECTRIC, ELECTRIC,
GRASSY, GRASSY,
PSYCHIC PSYCHIC,
} }
export class Terrain { export class Terrain {
@ -57,7 +57,10 @@ export class Terrain {
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
if (!move.hasAttr(ProtectAttr)) { if (!move.hasAttr(ProtectAttr)) {
// Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain // 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 ""; return "";
} }
export function getTerrainColor(terrainType: TerrainType): [number, number, number] { export function getTerrainColor(terrainType: TerrainType): [number, number, number] {
switch (terrainType) { switch (terrainType) {
case TerrainType.MISTY: case TerrainType.MISTY:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -271,7 +271,10 @@ export function getTypeDamageMultiplier(attackType: PokemonType, defType: Pokemo
* Retrieve the color corresponding to a specific damage multiplier * Retrieve the color corresponding to a specific damage multiplier
* @returns A color or undefined if the default color should be used * @returns A color or undefined if the default color should be used
*/ */
export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, side: "defense" | "offense"): string | undefined { export function getTypeDamageMultiplierColor(
multiplier: TypeDamageMultiplier,
side: "defense" | "offense",
): string | undefined {
if (side === "offense") { if (side === "offense") {
switch (multiplier) { switch (multiplier) {
case 0: case 0:
@ -291,7 +294,8 @@ export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, s
case 8: case 8:
return "#52C200"; return "#52C200";
} }
} else if (side === "defense") { }
if (side === "defense") {
switch (multiplier) { switch (multiplier) {
case 0: case 0:
return "#B1B100"; return "#B1B100";

View File

@ -106,9 +106,13 @@ export class Weather {
const field = globalScene.getField(true); const field = globalScene.getField(true);
for (const pokemon of field) { for (const pokemon of field) {
let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr)[0]; let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon
.getAbility()
.getAttrs(SuppressWeatherEffectAbAttr)[0];
if (!suppressWeatherEffectAbAttr) { if (!suppressWeatherEffectAbAttr) {
suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] : null; suppressWeatherEffectAbAttr = pokemon.hasPassive()
? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0]
: null;
} }
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) { if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) {
return true; return true;
@ -172,9 +176,13 @@ export function getWeatherLapseMessage(weatherType: WeatherType): string | null
export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string | null { export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
return i18next.t("weather:sandstormDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); return i18next.t("weather:sandstormDamageMessage", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
});
case WeatherType.HAIL: case WeatherType.HAIL:
return i18next.t("weather:hailDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); return i18next.t("weather:hailDamageMessage", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
});
} }
return null; return null;
@ -261,9 +269,14 @@ export function getTerrainClearMessage(terrainType: TerrainType): string | null
export function getTerrainBlockMessage(pokemon: Pokemon, terrainType: TerrainType): string { export function getTerrainBlockMessage(pokemon: Pokemon, terrainType: TerrainType): string {
if (terrainType === TerrainType.MISTY) { if (terrainType === TerrainType.MISTY) {
return i18next.t("terrain:mistyBlockMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); return i18next.t("terrain:mistyBlockMessage", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
});
} }
return i18next.t("terrain:defaultBlockMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), terrainName: getTerrainName(terrainType) }); return i18next.t("terrain:defaultBlockMessage", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
terrainName: getTerrainName(terrainType),
});
} }
export interface WeatherPoolEntry { export interface WeatherPoolEntry {
@ -276,9 +289,7 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
const hasSun = arena.getTimeOfDay() < 2; const hasSun = arena.getTimeOfDay() < 2;
switch (arena.biomeType) { switch (arena.biomeType) {
case Biome.GRASS: case Biome.GRASS:
weatherPool = [ weatherPool = [{ weatherType: WeatherType.NONE, weight: 7 }];
{ weatherType: WeatherType.NONE, weight: 7 }
];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 3 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 3 });
} }
@ -295,26 +306,26 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
case Biome.FOREST: case Biome.FOREST:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 8 }, { weatherType: WeatherType.NONE, weight: 8 },
{ weatherType: WeatherType.RAIN, weight: 5 } { weatherType: WeatherType.RAIN, weight: 5 },
]; ];
break; break;
case Biome.SEA: case Biome.SEA:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 3 }, { weatherType: WeatherType.NONE, weight: 3 },
{ weatherType: WeatherType.RAIN, weight: 12 } { weatherType: WeatherType.RAIN, weight: 12 },
]; ];
break; break;
case Biome.SWAMP: case Biome.SWAMP:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 3 }, { weatherType: WeatherType.NONE, weight: 3 },
{ weatherType: WeatherType.RAIN, weight: 4 }, { weatherType: WeatherType.RAIN, weight: 4 },
{ weatherType: WeatherType.FOG, weight: 1 } { weatherType: WeatherType.FOG, weight: 1 },
]; ];
break; break;
case Biome.BEACH: case Biome.BEACH:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 8 }, { weatherType: WeatherType.NONE, weight: 8 },
{ weatherType: WeatherType.RAIN, weight: 3 } { weatherType: WeatherType.RAIN, weight: 3 },
]; ];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 });
@ -324,27 +335,23 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 10 }, { weatherType: WeatherType.NONE, weight: 10 },
{ weatherType: WeatherType.RAIN, weight: 5 }, { weatherType: WeatherType.RAIN, weight: 5 },
{ weatherType: WeatherType.FOG, weight: 1 } { weatherType: WeatherType.FOG, weight: 1 },
]; ];
break; break;
case Biome.SEABED: case Biome.SEABED:
weatherPool = [ weatherPool = [{ weatherType: WeatherType.RAIN, weight: 1 }];
{ weatherType: WeatherType.RAIN, weight: 1 }
];
break; break;
case Biome.BADLANDS: case Biome.BADLANDS:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 8 }, { weatherType: WeatherType.NONE, weight: 8 },
{ weatherType: WeatherType.SANDSTORM, weight: 2 } { weatherType: WeatherType.SANDSTORM, weight: 2 },
]; ];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 });
} }
break; break;
case Biome.DESERT: case Biome.DESERT:
weatherPool = [ weatherPool = [{ weatherType: WeatherType.SANDSTORM, weight: 2 }];
{ weatherType: WeatherType.SANDSTORM, weight: 2 }
];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 });
} }
@ -353,37 +360,38 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 3 }, { weatherType: WeatherType.NONE, weight: 3 },
{ weatherType: WeatherType.SNOW, weight: 4 }, { weatherType: WeatherType.SNOW, weight: 4 },
{ weatherType: WeatherType.HAIL, weight: 1 } { weatherType: WeatherType.HAIL, weight: 1 },
]; ];
break; break;
case Biome.MEADOW: case Biome.MEADOW:
weatherPool = [ weatherPool = [{ weatherType: WeatherType.NONE, weight: 2 }];
{ weatherType: WeatherType.NONE, weight: 2 }
];
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 });
} }
case Biome.VOLCANO: case Biome.VOLCANO:
weatherPool = [ weatherPool = [
{ weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE, weight: 1 } {
weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE,
weight: 1,
},
]; ];
break; break;
case Biome.GRAVEYARD: case Biome.GRAVEYARD:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 3 }, { weatherType: WeatherType.NONE, weight: 3 },
{ weatherType: WeatherType.FOG, weight: 1 } { weatherType: WeatherType.FOG, weight: 1 },
]; ];
break; break;
case Biome.JUNGLE: case Biome.JUNGLE:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.NONE, weight: 8 }, { weatherType: WeatherType.NONE, weight: 8 },
{ weatherType: WeatherType.RAIN, weight: 2 } { weatherType: WeatherType.RAIN, weight: 2 },
]; ];
break; break;
case Biome.SNOWY_FOREST: case Biome.SNOWY_FOREST:
weatherPool = [ weatherPool = [
{ weatherType: WeatherType.SNOW, weight: 7 }, { weatherType: WeatherType.SNOW, weight: 7 },
{ weatherType: WeatherType.HAIL, weight: 1 } { weatherType: WeatherType.HAIL, weight: 1 },
]; ];
break; break;
case Biome.ISLAND: case Biome.ISLAND:
@ -403,7 +411,9 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
if (weatherPool.length > 1) { if (weatherPool.length > 1) {
let totalWeight = 0; let totalWeight = 0;
weatherPool.forEach(w => totalWeight += w.weight); for (const w of weatherPool) {
totalWeight += w.weight;
}
const rand = Utils.randSeedInt(totalWeight); const rand = Utils.randSeedInt(totalWeight);
let w = 0; let w = 0;
@ -415,7 +425,5 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
} }
} }
return weatherPool.length return weatherPool.length ? weatherPool[0].weatherType : WeatherType.NONE;
? weatherPool[0].weatherType
: WeatherType.NONE;
} }

View File

@ -3,7 +3,9 @@ export function getData() {
if (!dataStr) { if (!dataStr) {
return null; return null;
} }
return JSON.parse(atob(dataStr), (k, v) => k.endsWith("Attr") && ![ "natureAttr", "abilityAttr", "passiveAttr" ].includes(k) ? BigInt(v) : v); return JSON.parse(atob(dataStr), (k, v) =>
k.endsWith("Attr") && !["natureAttr", "abilityAttr", "passiveAttr"].includes(k) ? BigInt(v) : v,
);
} }
export function getSession() { export function getSession() {

View File

@ -48,9 +48,11 @@ export type TempBattleStat = typeof TEMP_BATTLE_STATS[number];
export function getStatStageChangeDescriptionKey(stages: number, isIncrease: boolean) { export function getStatStageChangeDescriptionKey(stages: number, isIncrease: boolean) {
if (stages === 1) { if (stages === 1) {
return isIncrease ? "battle:statRose" : "battle:statFell"; return isIncrease ? "battle:statRose" : "battle:statFell";
} else if (stages === 2) { }
if (stages === 2) {
return isIncrease ? "battle:statSharplyRose" : "battle:statHarshlyFell"; return isIncrease ? "battle:statSharplyRose" : "battle:statHarshlyFell";
} else if (stages > 2 && stages <= 6) { }
if (stages > 2 && stages <= 6) {
return isIncrease ? "battle:statRoseDrastically" : "battle:statSeverelyFell"; return isIncrease ? "battle:statRoseDrastically" : "battle:statSeverelyFell";
} }
return isIncrease ? "battle:statWontGoAnyHigher" : "battle:statWontGoAnyLower"; return isIncrease ? "battle:statWontGoAnyHigher" : "battle:statWontGoAnyLower";

View File

@ -76,7 +76,13 @@ export class TagAddedEvent extends ArenaEvent {
/** The maximum amount of layers of the arena trap. */ /** The maximum amount of layers of the arena trap. */
public arenaTagMaxLayers: number; public arenaTagMaxLayers: number;
constructor(arenaTagType: ArenaTagType, arenaTagSide: ArenaTagSide, duration: number, arenaTagLayers?: number, arenaTagMaxLayers?: number) { constructor(
arenaTagType: ArenaTagType,
arenaTagSide: ArenaTagSide,
duration: number,
arenaTagLayers?: number,
arenaTagMaxLayers?: number,
) {
super(ArenaEventType.TAG_ADDED, duration); super(ArenaEventType.TAG_ADDED, duration);
this.arenaTagType = arenaTagType; this.arenaTagType = arenaTagType;

View File

@ -3,7 +3,7 @@ export enum EggEventType {
* Triggers when egg count is changed. * Triggers when egg count is changed.
* @see {@linkcode MoveUsedEvent} * @see {@linkcode MoveUsedEvent}
*/ */
EGG_COUNT_CHANGED = "onEggCountChanged" EGG_COUNT_CHANGED = "onEggCountChanged",
} }
/** /**

View File

@ -24,13 +24,17 @@ export function addPokeballOpenParticles(x: number, y: number, pokeballType: Pok
} }
function doDefaultPbOpenParticles(x: number, y: number, radius: number) { function doDefaultPbOpenParticles(x: number, y: number, radius: number) {
const pbOpenParticlesFrameNames = globalScene.anims.generateFrameNames("pb_particles", { start: 0, end: 3, suffix: ".png" }); const pbOpenParticlesFrameNames = globalScene.anims.generateFrameNames("pb_particles", {
if (!(globalScene.anims.exists("pb_open_particle"))) { start: 0,
end: 3,
suffix: ".png",
});
if (!globalScene.anims.exists("pb_open_particle")) {
globalScene.anims.create({ globalScene.anims.create({
key: "pb_open_particle", key: "pb_open_particle",
frames: pbOpenParticlesFrameNames, frames: pbOpenParticlesFrameNames,
frameRate: 16, frameRate: 16,
repeat: -1 repeat: -1,
}); });
} }
@ -38,17 +42,17 @@ function doDefaultPbOpenParticles(x: number, y: number, radius: number) {
const particle = globalScene.add.sprite(x, y, "pb_open_particle"); const particle = globalScene.add.sprite(x, y, "pb_open_particle");
globalScene.field.add(particle); globalScene.field.add(particle);
const angle = index * 45; const angle = index * 45;
const [ xCoord, yCoord ] = [ radius * Math.cos(angle * Math.PI / 180), radius * Math.sin(angle * Math.PI / 180) ]; const [xCoord, yCoord] = [radius * Math.cos((angle * Math.PI) / 180), radius * Math.sin((angle * Math.PI) / 180)];
globalScene.tweens.add({ globalScene.tweens.add({
targets: particle, targets: particle,
x: x + xCoord, x: x + xCoord,
y: y + yCoord, y: y + yCoord,
duration: 575 duration: 575,
}); });
particle.play({ particle.play({
key: "pb_open_particle", key: "pb_open_particle",
startFrame: (index + 3) % 4, startFrame: (index + 3) % 4,
frameRate: Math.floor(16 * globalScene.gameSpeed) frameRate: Math.floor(16 * globalScene.gameSpeed),
}); });
globalScene.tweens.add({ globalScene.tweens.add({
targets: particle, targets: particle,
@ -56,7 +60,7 @@ function doDefaultPbOpenParticles(x: number, y: number, radius: number) {
duration: 75, duration: 75,
alpha: 0, alpha: 0,
ease: "Sine.easeIn", ease: "Sine.easeIn",
onComplete: () => particle.destroy() onComplete: () => particle.destroy(),
}); });
}; };
@ -64,7 +68,7 @@ function doDefaultPbOpenParticles(x: number, y: number, radius: number) {
globalScene.time.addEvent({ globalScene.time.addEvent({
delay: 20, delay: 20,
repeat: 16, repeat: 16,
callback: () => addParticle(++particleCount) callback: () => addParticle(++particleCount),
}); });
} }
@ -84,7 +88,7 @@ function doUbOpenParticles(x: number, y: number, frameIndex: number) {
for (const particle of particles) { for (const particle of particles) {
particle.destroy(); particle.destroy();
} }
} },
}); });
} }
@ -105,12 +109,20 @@ function doMbOpenParticles(x: number, y: number) {
for (const particle of particles) { for (const particle of particles) {
particle.destroy(); particle.destroy();
} }
} },
}); });
} }
} }
function doFanOutParticle(trigIndex: number, x: number, y: number, xSpeed: number, ySpeed: number, angle: number, frameIndex: number): Phaser.GameObjects.Image { function doFanOutParticle(
trigIndex: number,
x: number,
y: number,
xSpeed: number,
ySpeed: number,
angle: number,
frameIndex: number,
): Phaser.GameObjects.Image {
let f = 0; let f = 0;
const particle = globalScene.add.image(x, y, "pb_particles", `${frameIndex}.png`); const particle = globalScene.add.image(x, y, "pb_particles", `${frameIndex}.png`);
@ -122,7 +134,7 @@ function doFanOutParticle(trigIndex: number, x: number, y: number, xSpeed: numbe
} }
particle.x = x + sin(trigIndex, f * xSpeed); particle.x = x + sin(trigIndex, f * xSpeed);
particle.y = y + cos(trigIndex, f * ySpeed); particle.y = y + cos(trigIndex, f * ySpeed);
trigIndex = (trigIndex + angle); trigIndex = trigIndex + angle;
f++; f++;
}; };
@ -131,7 +143,7 @@ function doFanOutParticle(trigIndex: number, x: number, y: number, xSpeed: numbe
duration: getFrameMs(1), duration: getFrameMs(1),
onRepeat: () => { onRepeat: () => {
updateParticle(); updateParticle();
} },
}); });
return particle; return particle;
@ -155,16 +167,16 @@ export function addPokeballCaptureStars(pokeball: Phaser.GameObjects.Sprite): vo
y: pokeball.y, y: pokeball.y,
alpha: 0, alpha: 0,
ease: "Sine.easeIn", ease: "Sine.easeIn",
duration: 250 duration: 250,
}); });
} },
}); });
const dist = randGauss(25); const dist = randGauss(25);
globalScene.tweens.add({ globalScene.tweens.add({
targets: particle, targets: particle,
x: pokeball.x + dist, x: pokeball.x + dist,
duration: 500 duration: 500,
}); });
globalScene.tweens.add({ globalScene.tweens.add({
@ -172,7 +184,7 @@ export function addPokeballCaptureStars(pokeball: Phaser.GameObjects.Sprite): vo
alpha: 0, alpha: 0,
delay: 425, delay: 425,
duration: 75, duration: 75,
onComplete: () => particle.destroy() onComplete: () => particle.destroy(),
}); });
}; };
@ -200,7 +212,10 @@ export function doShinySparkleAnim(sparkleSprite: Phaser.GameObjects.Sprite, var
// Make sure the animation exists, and create it if not // Make sure the animation exists, and create it if not
if (!globalScene.anims.exists(animationKey)) { if (!globalScene.anims.exists(animationKey)) {
const frameNames = globalScene.anims.generateFrameNames(spriteKey, { suffix: ".png", end: 34 }); const frameNames = globalScene.anims.generateFrameNames(spriteKey, {
suffix: ".png",
end: 34,
});
globalScene.anims.create({ globalScene.anims.create({
key: `sparkle${keySuffix}`, key: `sparkle${keySuffix}`,
frames: frameNames, frames: frameNames,

View File

@ -5,7 +5,14 @@ import type { Constructor } from "#app/utils";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, getLegendaryWeatherContinuesMessage, Weather } from "#app/data/weather"; import {
getTerrainClearMessage,
getTerrainStartMessage,
getWeatherClearMessage,
getWeatherStartMessage,
getLegendaryWeatherContinuesMessage,
Weather,
} from "#app/data/weather";
import { CommonAnim } from "#app/data/battle-anims"; import { CommonAnim } from "#app/data/battle-anims";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
@ -19,7 +26,7 @@ import {
applyPostWeatherChangeAbAttrs, applyPostWeatherChangeAbAttrs,
PostTerrainChangeAbAttr, PostTerrainChangeAbAttr,
PostWeatherChangeAbAttr, PostWeatherChangeAbAttr,
TerrainEventTypeChangeAbAttr TerrainEventTypeChangeAbAttr,
} from "#app/data/ability"; } from "#app/data/ability";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
@ -58,7 +65,7 @@ export class Arena {
public readonly eventTarget: EventTarget = new EventTarget(); public readonly eventTarget: EventTarget = new EventTarget();
constructor(biome: Biome, bgm: string, playerFaints: number = 0) { constructor(biome: Biome, bgm: string, playerFaints = 0) {
this.biomeType = biome; this.biomeType = biome;
this.tags = []; this.tags = [];
this.bgm = bgm; this.bgm = bgm;
@ -88,19 +95,29 @@ export class Arena {
if (timeOfDay !== this.lastTimeOfDay) { if (timeOfDay !== this.lastTimeOfDay) {
this.pokemonPool = {}; this.pokemonPool = {};
for (const tier of Object.keys(biomePokemonPools[this.biomeType])) { for (const tier of Object.keys(biomePokemonPools[this.biomeType])) {
this.pokemonPool[tier] = Object.assign([], biomePokemonPools[this.biomeType][tier][TimeOfDay.ALL]).concat(biomePokemonPools[this.biomeType][tier][timeOfDay]); this.pokemonPool[tier] = Object.assign([], biomePokemonPools[this.biomeType][tier][TimeOfDay.ALL]).concat(
biomePokemonPools[this.biomeType][tier][timeOfDay],
);
} }
this.lastTimeOfDay = timeOfDay; this.lastTimeOfDay = timeOfDay;
} }
} }
randomSpecies(waveIndex: number, level: number, attempt?: number, luckValue?: number, isBoss?: boolean): PokemonSpecies { randomSpecies(
waveIndex: number,
level: number,
attempt?: number,
luckValue?: number,
isBoss?: boolean,
): PokemonSpecies {
const overrideSpecies = globalScene.gameMode.getOverrideSpecies(waveIndex); const overrideSpecies = globalScene.gameMode.getOverrideSpecies(waveIndex);
if (overrideSpecies) { if (overrideSpecies) {
return overrideSpecies; return overrideSpecies;
} }
const isBossSpecies = !!globalScene.getEncounterBossSegments(waveIndex, level) && !!this.pokemonPool[BiomePoolTier.BOSS].length const isBossSpecies =
&& (this.biomeType !== Biome.END || globalScene.gameMode.isClassic || globalScene.gameMode.isWaveFinal(waveIndex)); !!globalScene.getEncounterBossSegments(waveIndex, level) &&
!!this.pokemonPool[BiomePoolTier.BOSS].length &&
(this.biomeType !== Biome.END || globalScene.gameMode.isClassic || globalScene.gameMode.isWaveFinal(waveIndex));
const randVal = isBossSpecies ? 64 : 512; const randVal = isBossSpecies ? 64 : 512;
// luck influences encounter rarity // luck influences encounter rarity
let luckModifier = 0; let luckModifier = 0;
@ -109,8 +126,22 @@ export class Arena {
} }
const tierValue = Utils.randSeedInt(randVal - luckModifier); const tierValue = Utils.randSeedInt(randVal - luckModifier);
let tier = !isBossSpecies let tier = !isBossSpecies
? tierValue >= 156 ? BiomePoolTier.COMMON : tierValue >= 32 ? BiomePoolTier.UNCOMMON : tierValue >= 6 ? BiomePoolTier.RARE : tierValue >= 1 ? BiomePoolTier.SUPER_RARE : BiomePoolTier.ULTRA_RARE ? tierValue >= 156
: tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; ? BiomePoolTier.COMMON
: tierValue >= 32
? BiomePoolTier.UNCOMMON
: tierValue >= 6
? BiomePoolTier.RARE
: tierValue >= 1
? BiomePoolTier.SUPER_RARE
: BiomePoolTier.ULTRA_RARE
: tierValue >= 20
? BiomePoolTier.BOSS
: tierValue >= 6
? BiomePoolTier.BOSS_RARE
: tierValue >= 1
? BiomePoolTier.BOSS_SUPER_RARE
: BiomePoolTier.BOSS_ULTRA_RARE;
console.log(BiomePoolTier[tier]); console.log(BiomePoolTier[tier]);
while (!this.pokemonPool[tier].length) { while (!this.pokemonPool[tier].length) {
console.log(`Downgraded rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`); console.log(`Downgraded rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`);
@ -129,7 +160,7 @@ export class Arena {
} else { } else {
const levelThresholds = Object.keys(entry); const levelThresholds = Object.keys(entry);
for (let l = levelThresholds.length - 1; l >= 0; l--) { for (let l = levelThresholds.length - 1; l >= 0; l--) {
const levelThreshold = parseInt(levelThresholds[l]); const levelThreshold = Number.parseInt(levelThresholds[l]);
if (level >= levelThreshold) { if (level >= levelThreshold) {
const speciesIds = entry[levelThreshold]; const speciesIds = entry[levelThreshold];
if (speciesIds.length > 1) { if (speciesIds.length > 1) {
@ -146,13 +177,13 @@ export class Arena {
if (ret.subLegendary || ret.legendary || ret.mythical) { if (ret.subLegendary || ret.legendary || ret.mythical) {
switch (true) { switch (true) {
case (ret.baseTotal >= 720): case ret.baseTotal >= 720:
regen = level < 90; regen = level < 90;
break; break;
case (ret.baseTotal >= 670): case ret.baseTotal >= 670:
regen = level < 70; regen = level < 70;
break; break;
case (ret.baseTotal >= 580): case ret.baseTotal >= 580:
regen = level < 50; regen = level < 50;
break; break;
default: default:
@ -175,14 +206,29 @@ export class Arena {
return ret; return ret;
} }
randomTrainerType(waveIndex: number, isBoss: boolean = false): TrainerType { randomTrainerType(waveIndex: number, isBoss = false): TrainerType {
const isTrainerBoss = !!this.trainerPool[BiomePoolTier.BOSS].length const isTrainerBoss =
&& (globalScene.gameMode.isTrainerBoss(waveIndex, this.biomeType, globalScene.offsetGym) || isBoss); !!this.trainerPool[BiomePoolTier.BOSS].length &&
(globalScene.gameMode.isTrainerBoss(waveIndex, this.biomeType, globalScene.offsetGym) || isBoss);
console.log(isBoss, this.trainerPool); console.log(isBoss, this.trainerPool);
const tierValue = Utils.randSeedInt(!isTrainerBoss ? 512 : 64); const tierValue = Utils.randSeedInt(!isTrainerBoss ? 512 : 64);
let tier = !isTrainerBoss let tier = !isTrainerBoss
? tierValue >= 156 ? BiomePoolTier.COMMON : tierValue >= 32 ? BiomePoolTier.UNCOMMON : tierValue >= 6 ? BiomePoolTier.RARE : tierValue >= 1 ? BiomePoolTier.SUPER_RARE : BiomePoolTier.ULTRA_RARE ? tierValue >= 156
: tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; ? BiomePoolTier.COMMON
: tierValue >= 32
? BiomePoolTier.UNCOMMON
: tierValue >= 6
? BiomePoolTier.RARE
: tierValue >= 1
? BiomePoolTier.SUPER_RARE
: BiomePoolTier.ULTRA_RARE
: tierValue >= 20
? BiomePoolTier.BOSS
: tierValue >= 6
? BiomePoolTier.BOSS_RARE
: tierValue >= 1
? BiomePoolTier.BOSS_SUPER_RARE
: BiomePoolTier.BOSS_ULTRA_RARE;
console.log(BiomePoolTier[tier]); console.log(BiomePoolTier[tier]);
while (tier && !this.trainerPool[tier].length) { while (tier && !this.trainerPool[tier].length) {
console.log(`Downgraded trainer rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`); console.log(`Downgraded trainer rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`);
@ -274,14 +320,21 @@ export class Arena {
const oldWeatherType = this.weather?.weatherType || WeatherType.NONE; const oldWeatherType = this.weather?.weatherType || WeatherType.NONE;
if (this.weather?.isImmutable() && ![ WeatherType.HARSH_SUN, WeatherType.HEAVY_RAIN, WeatherType.STRONG_WINDS, WeatherType.NONE ].includes(weather)) { if (
globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (oldWeatherType - 1), true)); this.weather?.isImmutable() &&
![WeatherType.HARSH_SUN, WeatherType.HEAVY_RAIN, WeatherType.STRONG_WINDS, WeatherType.NONE].includes(weather)
) {
globalScene.unshiftPhase(
new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (oldWeatherType - 1), true),
);
globalScene.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!); globalScene.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!);
return false; return false;
} }
this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null; this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null;
this.eventTarget.dispatchEvent(new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType!, this.weather?.turnsLeft!)); // TODO: is this bang correct? this.eventTarget.dispatchEvent(
new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType!, this.weather?.turnsLeft!),
); // TODO: is this bang correct?
if (this.weather) { if (this.weather) {
globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1), true)); globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1), true));
@ -290,8 +343,13 @@ export class Arena {
globalScene.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct? globalScene.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct?
} }
globalScene.getField(true).filter(p => p.isOnField()).map(pokemon => { globalScene
pokemon.findAndRemoveTags(t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather)); .getField(true)
.filter(p => p.isOnField())
.map(pokemon => {
pokemon.findAndRemoveTags(
t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather),
);
applyPostWeatherChangeAbAttrs(PostWeatherChangeAbAttr, pokemon, weather); applyPostWeatherChangeAbAttrs(PostWeatherChangeAbAttr, pokemon, weather);
}); });
@ -303,8 +361,8 @@ export class Arena {
*/ */
triggerWeatherBasedFormChanges(): void { triggerWeatherBasedFormChanges(): void {
globalScene.getField(true).forEach(p => { globalScene.getField(true).forEach(p => {
const isCastformWithForecast = (p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM); const isCastformWithForecast = p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM;
const isCherrimWithFlowerGift = (p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM); const isCherrimWithFlowerGift = p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM;
if (isCastformWithForecast || isCherrimWithFlowerGift) { if (isCastformWithForecast || isCherrimWithFlowerGift) {
new ShowAbilityPhase(p.getBattlerIndex()); new ShowAbilityPhase(p.getBattlerIndex());
@ -318,8 +376,10 @@ export class Arena {
*/ */
triggerWeatherBasedFormChangesToNormal(): void { triggerWeatherBasedFormChangesToNormal(): void {
globalScene.getField(true).forEach(p => { globalScene.getField(true).forEach(p => {
const isCastformWithForecast = (p.hasAbility(Abilities.FORECAST, false, true) && p.species.speciesId === Species.CASTFORM); const isCastformWithForecast =
const isCherrimWithFlowerGift = (p.hasAbility(Abilities.FLOWER_GIFT, false, true) && p.species.speciesId === Species.CHERRIM); p.hasAbility(Abilities.FORECAST, false, true) && p.species.speciesId === Species.CASTFORM;
const isCherrimWithFlowerGift =
p.hasAbility(Abilities.FLOWER_GIFT, false, true) && p.species.speciesId === Species.CHERRIM;
if (isCastformWithForecast || isCherrimWithFlowerGift) { if (isCastformWithForecast || isCherrimWithFlowerGift) {
new ShowAbilityPhase(p.getBattlerIndex()); new ShowAbilityPhase(p.getBattlerIndex());
@ -328,7 +388,7 @@ export class Arena {
}); });
} }
trySetTerrain(terrain: TerrainType, hasPokemonSource: boolean, ignoreAnim: boolean = false): boolean { trySetTerrain(terrain: TerrainType, hasPokemonSource: boolean, ignoreAnim = false): boolean {
if (this.terrain?.terrainType === (terrain || undefined)) { if (this.terrain?.terrainType === (terrain || undefined)) {
return false; return false;
} }
@ -336,7 +396,9 @@ export class Arena {
const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE; const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE;
this.terrain = terrain ? new Terrain(terrain, hasPokemonSource ? 5 : 0) : null; this.terrain = terrain ? new Terrain(terrain, hasPokemonSource ? 5 : 0) : null;
this.eventTarget.dispatchEvent(new TerrainChangedEvent(oldTerrainType, this.terrain?.terrainType!, this.terrain?.turnsLeft!)); // TODO: are those bangs correct? this.eventTarget.dispatchEvent(
new TerrainChangedEvent(oldTerrainType, this.terrain?.terrainType!, this.terrain?.turnsLeft!),
); // TODO: are those bangs correct?
if (this.terrain) { if (this.terrain) {
if (!ignoreAnim) { if (!ignoreAnim) {
@ -347,8 +409,13 @@ export class Arena {
globalScene.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct? globalScene.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct?
} }
globalScene.getField(true).filter(p => p.isOnField()).map(pokemon => { globalScene
pokemon.findAndRemoveTags(t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain)); .getField(true)
.filter(p => p.isOnField())
.map(pokemon => {
pokemon.findAndRemoveTags(
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
);
applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain); applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain);
applyAbAttrs(TerrainEventTypeChangeAbAttr, pokemon, null, false); applyAbAttrs(TerrainEventTypeChangeAbAttr, pokemon, null, false);
}); });
@ -479,15 +546,12 @@ export class Arena {
switch (Overrides.ARENA_TINT_OVERRIDE) { switch (Overrides.ARENA_TINT_OVERRIDE) {
case TimeOfDay.DUSK: case TimeOfDay.DUSK:
return [98, 48, 73].map(c => Math.round((c + 128) / 2)) as [number, number, number]; return [98, 48, 73].map(c => Math.round((c + 128) / 2)) as [number, number, number];
break; case TimeOfDay.NIGHT:
case (TimeOfDay.NIGHT):
return [64, 64, 64]; return [64, 64, 64];
break;
case TimeOfDay.DAWN: case TimeOfDay.DAWN:
case TimeOfDay.DAY: case TimeOfDay.DAY:
default: default:
return [128, 128, 128]; return [128, 128, 128];
break;
} }
} }
@ -550,8 +614,14 @@ export class Arena {
* @param simulated if `true`, this applies arena tags without changing game state * @param simulated if `true`, this applies arena tags without changing game state
* @param args array of parameters that the called upon tags may need * @param args array of parameters that the called upon tags may need
*/ */
applyTagsForSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide, simulated: boolean, ...args: unknown[]): void { applyTagsForSide(
let tags = typeof tagType === "string" tagType: ArenaTagType | Constructor<ArenaTag>,
side: ArenaTagSide,
simulated: boolean,
...args: unknown[]
): void {
let tags =
typeof tagType === "string"
? this.tags.filter(t => t.tagType === tagType) ? this.tags.filter(t => t.tagType === tagType)
: this.tags.filter(t => t instanceof tagType); : this.tags.filter(t => t instanceof tagType);
if (side !== ArenaTagSide.BOTH) { if (side !== ArenaTagSide.BOTH) {
@ -582,7 +652,15 @@ export class Arena {
* @param targetIndex The {@linkcode BattlerIndex} of the target pokemon * @param targetIndex The {@linkcode BattlerIndex} of the target pokemon
* @returns `false` if there already exists a tag of this type in the Arena * @returns `false` if there already exists a tag of this type in the Arena
*/ */
addTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, side: ArenaTagSide = ArenaTagSide.BOTH, quiet: boolean = false, targetIndex?: BattlerIndex): boolean { addTag(
tagType: ArenaTagType,
turnCount: number,
sourceMove: Moves | undefined,
sourceId: number,
side: ArenaTagSide = ArenaTagSide.BOTH,
quiet = false,
targetIndex?: BattlerIndex,
): boolean {
const existingTag = this.getTagOnSide(tagType, side); const existingTag = this.getTagOnSide(tagType, side);
if (existingTag) { if (existingTag) {
existingTag.onOverlap(this); existingTag.onOverlap(this);
@ -603,7 +681,9 @@ export class Arena {
const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {}; const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {};
this.eventTarget.dispatchEvent(new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount, layers, maxLayers)); this.eventTarget.dispatchEvent(
new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount, layers, maxLayers),
);
} }
return true; return true;
@ -631,9 +711,13 @@ export class Arena {
* @returns either the {@linkcode ArenaTag}, or `undefined` if it isn't there * @returns either the {@linkcode ArenaTag}, or `undefined` if it isn't there
*/ */
getTagOnSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide): ArenaTag | undefined { getTagOnSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide): ArenaTag | undefined {
return typeof(tagType) === "string" return typeof tagType === "string"
? this.tags.find(t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)) ? this.tags.find(
: this.tags.find(t => t instanceof tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)); t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side),
)
: this.tags.find(
t => t instanceof tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side),
);
} }
/** /**
@ -652,11 +736,15 @@ export class Arena {
* @returns array of {@linkcode ArenaTag}s from which the Arena's tags return `true` and apply to the given side * @returns array of {@linkcode ArenaTag}s from which the Arena's tags return `true` and apply to the given side
*/ */
findTagsOnSide(tagPredicate: (t: ArenaTag) => boolean, side: ArenaTagSide): ArenaTag[] { findTagsOnSide(tagPredicate: (t: ArenaTag) => boolean, side: ArenaTagSide): ArenaTag[] {
return this.tags.filter(t => tagPredicate(t) && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)); return this.tags.filter(
t => tagPredicate(t) && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side),
);
} }
lapseTags(): void { lapseTags(): void {
this.tags.filter(t => !(t.lapse(this))).forEach(t => { this.tags
.filter(t => !t.lapse(this))
.forEach(t => {
t.onRemove(this); t.onRemove(this);
this.tags.splice(this.tags.indexOf(t), 1); this.tags.splice(this.tags.indexOf(t), 1);
@ -676,7 +764,7 @@ export class Arena {
return !!tag; return !!tag;
} }
removeTagOnSide(tagType: ArenaTagType, side: ArenaTagSide, quiet: boolean = false): boolean { removeTagOnSide(tagType: ArenaTagType, side: ArenaTagSide, quiet = false): boolean {
const tag = this.getTagOnSide(tagType, side); const tag = this.getTagOnSide(tagType, side);
if (tag) { if (tag) {
tag.onRemove(this, quiet); tag.onRemove(this, quiet);
@ -687,11 +775,12 @@ export class Arena {
return !!tag; return !!tag;
} }
removeAllTags(): void { removeAllTags(): void {
while (this.tags.length) { while (this.tags.length) {
this.tags[0].onRemove(this); this.tags[0].onRemove(this);
this.eventTarget.dispatchEvent(new TagRemovedEvent(this.tags[0].tagType, this.tags[0].side, this.tags[0].turnCount)); this.eventTarget.dispatchEvent(
new TagRemovedEvent(this.tags[0].tagType, this.tags[0].side, this.tags[0].turnCount),
);
this.tags.splice(0, 1); this.tags.splice(0, 1);
} }
@ -726,7 +815,7 @@ export class Arena {
case Biome.TALL_GRASS: case Biome.TALL_GRASS:
return 9.608; return 9.608;
case Biome.METROPOLIS: case Biome.METROPOLIS:
return 141.470; return 141.47;
case Biome.FOREST: case Biome.FOREST:
return 0.341; return 0.341;
case Biome.SEA: case Biome.SEA:
@ -738,17 +827,17 @@ export class Arena {
case Biome.LAKE: case Biome.LAKE:
return 7.215; return 7.215;
case Biome.SEABED: case Biome.SEABED:
return 2.600; return 2.6;
case Biome.MOUNTAIN: case Biome.MOUNTAIN:
return 4.018; return 4.018;
case Biome.BADLANDS: case Biome.BADLANDS:
return 17.790; return 17.79;
case Biome.CAVE: case Biome.CAVE:
return 14.240; return 14.24;
case Biome.DESERT: case Biome.DESERT:
return 1.143; return 1.143;
case Biome.ICE_CAVE: case Biome.ICE_CAVE:
return 0.000; return 0.0;
case Biome.MEADOW: case Biome.MEADOW:
return 3.891; return 3.891;
case Biome.POWER_PLANT: case Biome.POWER_PLANT:
@ -762,17 +851,17 @@ export class Arena {
case Biome.FACTORY: case Biome.FACTORY:
return 4.985; return 4.985;
case Biome.RUINS: case Biome.RUINS:
return 0.000; return 0.0;
case Biome.WASTELAND: case Biome.WASTELAND:
return 6.336; return 6.336;
case Biome.ABYSS: case Biome.ABYSS:
return 5.130; return 5.13;
case Biome.SPACE: case Biome.SPACE:
return 20.036; return 20.036;
case Biome.CONSTRUCTION_SITE: case Biome.CONSTRUCTION_SITE:
return 1.222; return 1.222;
case Biome.JUNGLE: case Biome.JUNGLE:
return 0.000; return 0.0;
case Biome.FAIRY_CAVE: case Biome.FAIRY_CAVE:
return 4.542; return 4.542;
case Biome.TEMPLE: case Biome.TEMPLE:
@ -782,7 +871,7 @@ export class Arena {
case Biome.LABORATORY: case Biome.LABORATORY:
return 114.862; return 114.862;
case Biome.SLUM: case Biome.SLUM:
return 0.000; return 0.0;
case Biome.SNOWY_FOREST: case Biome.SNOWY_FOREST:
return 3.047; return 3.047;
case Biome.END: case Biome.END:
@ -850,13 +939,14 @@ export class ArenaBase extends Phaser.GameObjects.Container {
this.base = globalScene.addFieldSprite(0, 0, "plains_a", undefined, 1); this.base = globalScene.addFieldSprite(0, 0, "plains_a", undefined, 1);
this.base.setOrigin(0, 0); this.base.setOrigin(0, 0);
this.props = !player ? this.props = !player
new Array(3).fill(null).map(() => { ? new Array(3).fill(null).map(() => {
const ret = globalScene.addFieldSprite(0, 0, "plains_b", undefined, 1); const ret = globalScene.addFieldSprite(0, 0, "plains_b", undefined, 1);
ret.setOrigin(0, 0); ret.setOrigin(0, 0);
ret.setVisible(false); ret.setVisible(false);
return ret; return ret;
}) : []; })
: [];
} }
setBiome(biome: Biome, propValue?: number): void { setBiome(biome: Biome, propValue?: number): void {
@ -868,13 +958,18 @@ export class ArenaBase extends Phaser.GameObjects.Container {
this.base.setTexture(baseKey); this.base.setTexture(baseKey);
if (this.base.texture.frameTotal > 1) { if (this.base.texture.frameTotal > 1) {
const baseFrameNames = globalScene.anims.generateFrameNames(baseKey, { zeroPad: 4, suffix: ".png", start: 1, end: this.base.texture.frameTotal - 1 }); const baseFrameNames = globalScene.anims.generateFrameNames(baseKey, {
if (!(globalScene.anims.exists(baseKey))) { zeroPad: 4,
suffix: ".png",
start: 1,
end: this.base.texture.frameTotal - 1,
});
if (!globalScene.anims.exists(baseKey)) {
globalScene.anims.create({ globalScene.anims.create({
key: baseKey, key: baseKey,
frames: baseFrameNames, frames: baseFrameNames,
frameRate: 12, frameRate: 12,
repeat: -1 repeat: -1,
}); });
} }
this.base.play(baseKey); this.base.play(baseKey);
@ -886,22 +981,26 @@ export class ArenaBase extends Phaser.GameObjects.Container {
} }
if (!this.player) { if (!this.player) {
globalScene.executeWithSeedOffset(() => { globalScene.executeWithSeedOffset(
this.propValue = propValue === undefined () => {
? hasProps ? Utils.randSeedInt(8) : 0 this.propValue = propValue === undefined ? (hasProps ? Utils.randSeedInt(8) : 0) : propValue;
: propValue;
this.props.forEach((prop, p) => { this.props.forEach((prop, p) => {
const propKey = `${biomeKey}_b${hasProps ? `_${p + 1}` : ""}`; const propKey = `${biomeKey}_b${hasProps ? `_${p + 1}` : ""}`;
prop.setTexture(propKey); prop.setTexture(propKey);
if (hasProps && prop.texture.frameTotal > 1) { if (hasProps && prop.texture.frameTotal > 1) {
const propFrameNames = globalScene.anims.generateFrameNames(propKey, { zeroPad: 4, suffix: ".png", start: 1, end: prop.texture.frameTotal - 1 }); const propFrameNames = globalScene.anims.generateFrameNames(propKey, {
if (!(globalScene.anims.exists(propKey))) { zeroPad: 4,
suffix: ".png",
start: 1,
end: prop.texture.frameTotal - 1,
});
if (!globalScene.anims.exists(propKey)) {
globalScene.anims.create({ globalScene.anims.create({
key: propKey, key: propKey,
frames: propFrameNames, frames: propFrameNames,
frameRate: 12, frameRate: 12,
repeat: -1 repeat: -1,
}); });
} }
prop.play(propKey); prop.play(propKey);
@ -912,7 +1011,10 @@ export class ArenaBase extends Phaser.GameObjects.Container {
prop.setVisible(hasProps && !!(this.propValue & (1 << p))); prop.setVisible(hasProps && !!(this.propValue & (1 << p)));
this.add(prop); this.add(prop);
}); });
}, globalScene.currentBattle?.waveIndex || 0, globalScene.waveSeed); },
globalScene.currentBattle?.waveIndex || 0,
globalScene.waveSeed,
);
} }
} }
} }

View File

@ -15,14 +15,24 @@ export default class DamageNumberHandler {
this.damageNumbers = new Map(); this.damageNumbers = new Map();
} }
add(target: Pokemon, amount: number, result: DamageResult | HitResult.HEAL = HitResult.EFFECTIVE, critical: boolean = false): void { add(
target: Pokemon,
amount: number,
result: DamageResult | HitResult.HEAL = HitResult.EFFECTIVE,
critical = false,
): void {
if (!globalScene?.damageNumbersMode) { if (!globalScene?.damageNumbersMode) {
return; return;
} }
const battlerIndex = target.getBattlerIndex(); const battlerIndex = target.getBattlerIndex();
const baseScale = target.getSpriteScale() / 6; const baseScale = target.getSpriteScale() / 6;
const damageNumber = addTextObject(target.x, -(globalScene.game.canvas.height / 6) + target.y - target.getSprite().height / 2, Utils.formatStat(amount, true), TextStyle.SUMMARY); const damageNumber = addTextObject(
target.x,
-(globalScene.game.canvas.height / 6) + target.y - target.getSprite().height / 2,
Utils.formatStat(amount, true),
TextStyle.SUMMARY,
);
damageNumber.setName("text-damage-number"); damageNumber.setName("text-damage-number");
damageNumber.setOrigin(0.5, 1); damageNumber.setOrigin(0.5, 1);
damageNumber.setScale(baseScale); damageNumber.setScale(baseScale);
@ -77,7 +87,7 @@ export default class DamageNumberHandler {
targets: damageNumber, targets: damageNumber,
duration: Utils.fixedInt(750), duration: Utils.fixedInt(750),
alpha: 1, alpha: 1,
y: "-=32" y: "-=32",
}); });
globalScene.tweens.add({ globalScene.tweens.add({
delay: 375, delay: 375,
@ -88,7 +98,7 @@ export default class DamageNumberHandler {
onComplete: () => { onComplete: () => {
this.damageNumbers.get(battlerIndex)!.splice(this.damageNumbers.get(battlerIndex)!.indexOf(damageNumber), 1); this.damageNumbers.get(battlerIndex)!.splice(this.damageNumbers.get(battlerIndex)!.indexOf(damageNumber), 1);
damageNumber.destroy(true); damageNumber.destroy(true);
} },
}); });
return; return;
} }
@ -104,7 +114,7 @@ export default class DamageNumberHandler {
scaleX: 0.75 * baseScale, scaleX: 0.75 * baseScale,
scaleY: 1.25 * baseScale, scaleY: 1.25 * baseScale,
y: "-=16", y: "-=16",
ease: "Cubic.easeOut" ease: "Cubic.easeOut",
}, },
{ {
duration: Utils.fixedInt(175), duration: Utils.fixedInt(175),
@ -112,69 +122,71 @@ export default class DamageNumberHandler {
scaleX: 0.875 * baseScale, scaleX: 0.875 * baseScale,
scaleY: 1.125 * baseScale, scaleY: 1.125 * baseScale,
y: "+=16", y: "+=16",
ease: "Cubic.easeIn" ease: "Cubic.easeIn",
}, },
{ {
duration: Utils.fixedInt(100), duration: Utils.fixedInt(100),
scaleX: 1.25 * baseScale, scaleX: 1.25 * baseScale,
scaleY: 0.75 * baseScale, scaleY: 0.75 * baseScale,
ease: "Cubic.easeOut" ease: "Cubic.easeOut",
}, },
{ {
duration: Utils.fixedInt(175), duration: Utils.fixedInt(175),
scaleX: 0.875 * baseScale, scaleX: 0.875 * baseScale,
scaleY: 1.125 * baseScale, scaleY: 1.125 * baseScale,
y: "-=8", y: "-=8",
ease: "Cubic.easeOut" ease: "Cubic.easeOut",
}, },
{ {
duration: Utils.fixedInt(50), duration: Utils.fixedInt(50),
scaleX: 0.925 * baseScale, scaleX: 0.925 * baseScale,
scaleY: 1.075 * baseScale, scaleY: 1.075 * baseScale,
y: "+=8", y: "+=8",
ease: "Cubic.easeIn" ease: "Cubic.easeIn",
}, },
{ {
duration: Utils.fixedInt(100), duration: Utils.fixedInt(100),
scaleX: 1.125 * baseScale, scaleX: 1.125 * baseScale,
scaleY: 0.875 * baseScale, scaleY: 0.875 * baseScale,
ease: "Cubic.easeOut" ease: "Cubic.easeOut",
}, },
{ {
duration: Utils.fixedInt(175), duration: Utils.fixedInt(175),
scaleX: 0.925 * baseScale, scaleX: 0.925 * baseScale,
scaleY: 1.075 * baseScale, scaleY: 1.075 * baseScale,
y: "-=4", y: "-=4",
ease: "Cubic.easeOut" ease: "Cubic.easeOut",
}, },
{ {
duration: Utils.fixedInt(50), duration: Utils.fixedInt(50),
scaleX: 0.975 * baseScale, scaleX: 0.975 * baseScale,
scaleY: 1.025 * baseScale, scaleY: 1.025 * baseScale,
y: "+=4", y: "+=4",
ease: "Cubic.easeIn" ease: "Cubic.easeIn",
}, },
{ {
duration: Utils.fixedInt(100), duration: Utils.fixedInt(100),
scaleX: 1.075 * baseScale, scaleX: 1.075 * baseScale,
scaleY: 0.925 * baseScale, scaleY: 0.925 * baseScale,
ease: "Cubic.easeOut" ease: "Cubic.easeOut",
}, },
{ {
duration: Utils.fixedInt(25), duration: Utils.fixedInt(25),
scaleX: baseScale, scaleX: baseScale,
scaleY: baseScale, scaleY: baseScale,
ease: "Cubic.easeOut" ease: "Cubic.easeOut",
}, },
{ {
delay: Utils.fixedInt(500), delay: Utils.fixedInt(500),
alpha: 0, alpha: 0,
onComplete: () => { onComplete: () => {
this.damageNumbers.get(battlerIndex)!.splice(this.damageNumbers.get(battlerIndex)!.indexOf(damageNumber), 1); this.damageNumbers
.get(battlerIndex)!
.splice(this.damageNumbers.get(battlerIndex)!.indexOf(damageNumber), 1);
damageNumber.destroy(true); damageNumber.destroy(true);
} },
} },
] ],
}); });
} }
} }

View File

@ -36,7 +36,7 @@ export class MysteryEncounterSpriteConfig {
/** The sprite key (which is the image file name). e.g. "ace_trainer_f" */ /** The sprite key (which is the image file name). e.g. "ace_trainer_f" */
spriteKey: string; spriteKey: string;
/** Refer to [/public/images](../../public/images) directorty for all folder names */ /** Refer to [/public/images](../../public/images) directorty for all folder names */
fileRoot: KnownFileRoot & string | string; fileRoot: (KnownFileRoot & string) | string;
/** Optional replacement for `spriteKey`/`fileRoot`. Just know this defaults to male/genderless, form 0, no shiny */ /** Optional replacement for `spriteKey`/`fileRoot`. Just know this defaults to male/genderless, form 0, no shiny */
species?: Species; species?: Species;
/** Enable shadow. Defaults to `false` */ /** Enable shadow. Defaults to `false` */
@ -80,7 +80,10 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
public encounter: MysteryEncounter; public encounter: MysteryEncounter;
public spriteConfigs: MysteryEncounterSpriteConfig[]; public spriteConfigs: MysteryEncounterSpriteConfig[];
public enterFromRight: boolean; public enterFromRight: boolean;
private shinySparkleSprites: { sprite: Phaser.GameObjects.Sprite, variant: Variant }[]; private shinySparkleSprites: {
sprite: Phaser.GameObjects.Sprite;
variant: Variant;
}[];
constructor(encounter: MysteryEncounter) { constructor(encounter: MysteryEncounter) {
super(globalScene, -72, 76); super(globalScene, -72, 76);
@ -89,7 +92,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
// Shallow copy configs to allow visual config updates at runtime without dirtying master copy of Encounter // Shallow copy configs to allow visual config updates at runtime without dirtying master copy of Encounter
this.spriteConfigs = encounter.spriteConfigs.map(config => { this.spriteConfigs = encounter.spriteConfigs.map(config => {
const result = { const result = {
...config ...config,
}; };
if (!isNullOrUndefined(result.species)) { if (!isNullOrUndefined(result.species)) {
@ -108,14 +111,22 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
const getSprite = (spriteKey: string, hasShadow?: boolean, yShadow?: number) => { const getSprite = (spriteKey: string, hasShadow?: boolean, yShadow?: number) => {
const ret = globalScene.addFieldSprite(0, 0, spriteKey); const ret = globalScene.addFieldSprite(0, 0, spriteKey);
ret.setOrigin(0.5, 1); ret.setOrigin(0.5, 1);
ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, yShadowOffset: yShadow ?? 0 }); ret.setPipeline(globalScene.spritePipeline, {
tone: [0.0, 0.0, 0.0, 0.0],
hasShadow: !!hasShadow,
yShadowOffset: yShadow ?? 0,
});
return ret; return ret;
}; };
const getItemSprite = (spriteKey: string, hasShadow?: boolean, yShadow?: number) => { const getItemSprite = (spriteKey: string, hasShadow?: boolean, yShadow?: number) => {
const icon = globalScene.add.sprite(-19, 2, "items", spriteKey); const icon = globalScene.add.sprite(-19, 2, "items", spriteKey);
icon.setOrigin(0.5, 1); icon.setOrigin(0.5, 1);
icon.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, yShadowOffset: yShadow ?? 0 }); icon.setPipeline(globalScene.spritePipeline, {
tone: [0.0, 0.0, 0.0, 0.0],
hasShadow: !!hasShadow,
yShadowOffset: yShadow ?? 0,
});
return icon; return icon;
}; };
@ -129,7 +140,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
this.shinySparkleSprites = []; this.shinySparkleSprites = [];
const shinySparkleSprites = globalScene.add.container(0, 0); const shinySparkleSprites = globalScene.add.container(0, 0);
this.spriteConfigs?.forEach((config) => { this.spriteConfigs?.forEach(config => {
const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha, isPokemon, isShiny, variant } = config; const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha, isPokemon, isShiny, variant } = config;
let sprite: GameObjects.Sprite; let sprite: GameObjects.Sprite;
@ -154,7 +165,10 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
pokemonShinySparkle = globalScene.add.sprite(sprite.x, sprite.y, "shiny"); pokemonShinySparkle = globalScene.add.sprite(sprite.x, sprite.y, "shiny");
pokemonShinySparkle.setOrigin(0.5, 1); pokemonShinySparkle.setOrigin(0.5, 1);
pokemonShinySparkle.setVisible(false); pokemonShinySparkle.setVisible(false);
this.shinySparkleSprites.push({ sprite: pokemonShinySparkle, variant: variant ?? 0 }); this.shinySparkleSprites.push({
sprite: pokemonShinySparkle,
variant: variant ?? 0,
});
shinySparkleSprites.add(pokemonShinySparkle); shinySparkleSprites.add(pokemonShinySparkle);
} }
} }
@ -216,7 +230,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
} }
const shinyPromises: Promise<void>[] = []; const shinyPromises: Promise<void>[] = [];
this.spriteConfigs.forEach((config) => { this.spriteConfigs.forEach(config => {
if (config.isPokemon) { if (config.isPokemon) {
globalScene.loadPokemonAtlas(config.spriteKey, config.fileRoot); globalScene.loadPokemonAtlas(config.spriteKey, config.fileRoot);
if (config.isShiny) { if (config.isShiny) {
@ -230,7 +244,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
}); });
globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => { globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => {
this.spriteConfigs.every((config) => { this.spriteConfigs.every(config => {
if (config.isItem) { if (config.isItem) {
return true; return true;
} }
@ -238,17 +252,21 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
const originalWarn = console.warn; const originalWarn = console.warn;
// Ignore warnings for missing frames, because there will be a lot // Ignore warnings for missing frames, because there will be a lot
console.warn = () => { console.warn = () => {};
}; const frameNames = globalScene.anims.generateFrameNames(config.spriteKey, {
const frameNames = globalScene.anims.generateFrameNames(config.spriteKey, { zeroPad: 4, suffix: ".png", start: 1, end: 128 }); zeroPad: 4,
suffix: ".png",
start: 1,
end: 128,
});
console.warn = originalWarn; console.warn = originalWarn;
if (!(globalScene.anims.exists(config.spriteKey))) { if (!globalScene.anims.exists(config.spriteKey)) {
globalScene.anims.create({ globalScene.anims.create({
key: config.spriteKey, key: config.spriteKey,
frames: frameNames, frames: frameNames,
frameRate: 10, frameRate: 10,
repeat: -1 repeat: -1,
}); });
} }
@ -313,7 +331,11 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
* @param animConfig {@linkcode Phaser.Types.Animations.PlayAnimationConfig} to pass to {@linkcode Phaser.GameObjects.Sprite.play} * @param animConfig {@linkcode Phaser.Types.Animations.PlayAnimationConfig} to pass to {@linkcode Phaser.GameObjects.Sprite.play}
* @returns true if the sprite was able to be animated * @returns true if the sprite was able to be animated
*/ */
tryPlaySprite(sprite: Phaser.GameObjects.Sprite, tintSprite: Phaser.GameObjects.Sprite, animConfig: Phaser.Types.Animations.PlayAnimationConfig): boolean { tryPlaySprite(
sprite: Phaser.GameObjects.Sprite,
tintSprite: Phaser.GameObjects.Sprite,
animConfig: Phaser.Types.Animations.PlayAnimationConfig,
): boolean {
// Show an error in the console if there isn't a texture loaded // Show an error in the console if there isn't a texture loaded
if (sprite.texture.key === "__MISSING") { if (sprite.texture.key === "__MISSING") {
console.error(`No texture found for '${animConfig.key}'!`); console.error(`No texture found for '${animConfig.key}'!`);
@ -359,7 +381,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
const trainerAnimConfig: PlayAnimationConfig = { const trainerAnimConfig: PlayAnimationConfig = {
key: config.spriteKey, key: config.spriteKey,
repeat: config?.repeat ? -1 : 0, repeat: config?.repeat ? -1 : 0,
startFrame: config?.startFrame ?? 0 startFrame: config?.startFrame ?? 0,
}; };
this.tryPlaySprite(sprites[i], tintSprites[i], trainerAnimConfig); this.tryPlaySprite(sprites[i], tintSprites[i], trainerAnimConfig);
@ -392,7 +414,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
} }
const ret: Phaser.GameObjects.Sprite[] = []; const ret: Phaser.GameObjects.Sprite[] = [];
this.spriteConfigs.forEach((config, i) => { this.spriteConfigs.forEach((_, i) => {
ret.push(this.getAt(i * 2)); ret.push(this.getAt(i * 2));
}); });
return ret; return ret;
@ -407,7 +429,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
} }
const ret: Phaser.GameObjects.Sprite[] = []; const ret: Phaser.GameObjects.Sprite[] = [];
this.spriteConfigs.forEach((config, i) => { this.spriteConfigs.forEach((_, i) => {
ret.push(this.getAt(i * 2 + 1)); ret.push(this.getAt(i * 2 + 1));
}); });
@ -434,7 +456,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
targets: sprite, targets: sprite,
alpha: alpha || 1, alpha: alpha || 1,
duration: duration, duration: duration,
ease: ease || "Linear" ease: ease || "Linear",
}); });
} else { } else {
sprite.setAlpha(alpha); sprite.setAlpha(alpha);
@ -471,7 +493,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
onComplete: () => { onComplete: () => {
sprite.setVisible(false); sprite.setVisible(false);
sprite.setAlpha(1); sprite.setAlpha(1);
} },
}); });
} else { } else {
sprite.setVisible(false); sprite.setVisible(false);
@ -497,9 +519,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
* @param value - true for visible, false for hidden * @param value - true for visible, false for hidden
*/ */
setVisible(value: boolean): this { setVisible(value: boolean): this {
this.getSprites().forEach(sprite => { for (const sprite of this.getSprites()) {
sprite.setVisible(value); sprite.setVisible(value);
}); }
return super.setVisible(value); return super.setVisible(value);
} }
} }

View File

@ -14,12 +14,14 @@ export default class PokemonSpriteSparkleHandler {
to: 1, to: 1,
yoyo: true, yoyo: true,
repeat: -1, repeat: -1,
onRepeat: () => this.onLapse() onRepeat: () => this.onLapse(),
}); });
} }
onLapse(): void { onLapse(): void {
Array.from(this.sprites.values()).filter(s => !s.scene).map(s => this.sprites.delete(s)); Array.from(this.sprites.values())
.filter(s => !s.scene)
.map(s => this.sprites.delete(s));
for (const s of this.sprites.values()) { for (const s of this.sprites.values()) {
if (!s.pipelineData["teraColor"] || !(s.pipelineData["teraColor"] as number[]).find(c => c)) { if (!s.pipelineData["teraColor"] || !(s.pipelineData["teraColor"] as number[]).find(c => c)) {
continue; continue;
@ -30,7 +32,7 @@ export default class PokemonSpriteSparkleHandler {
if (!(s.parentContainer instanceof Pokemon) || !(s.parentContainer as Pokemon).isTerastallized) { if (!(s.parentContainer instanceof Pokemon) || !(s.parentContainer as Pokemon).isTerastallized) {
continue; continue;
} }
const pokemon = s.parentContainer instanceof Pokemon ? s.parentContainer as Pokemon : null; const pokemon = s.parentContainer instanceof Pokemon ? (s.parentContainer as Pokemon) : null;
const parent = (pokemon || s).parentContainer; const parent = (pokemon || s).parentContainer;
const texture = s.texture; const texture = s.texture;
const [width, height] = [texture.source[0].width, texture.source[0].height]; const [width, height] = [texture.source[0].width, texture.source[0].height];
@ -40,7 +42,11 @@ export default class PokemonSpriteSparkleHandler {
const pixel = texture.manager.getPixel(pixelX, pixelY, texture.key, "__BASE"); const pixel = texture.manager.getPixel(pixelX, pixelY, texture.key, "__BASE");
if (pixel?.alpha) { if (pixel?.alpha) {
const [xOffset, yOffset] = [-s.originX * s.width, -s.originY * s.height]; const [xOffset, yOffset] = [-s.originX * s.width, -s.originY * s.height];
const sparkle = globalScene.addFieldSprite(((pokemon?.x || 0) + s.x + pixelX * ratioX + xOffset), ((pokemon?.y || 0) + s.y + pixelY * ratioY + yOffset), "tera_sparkle"); const sparkle = globalScene.addFieldSprite(
(pokemon?.x || 0) + s.x + pixelX * ratioX + xOffset,
(pokemon?.y || 0) + s.y + pixelY * ratioY + yOffset,
"tera_sparkle",
);
sparkle.pipelineData["ignoreTimeTint"] = s.pipelineData["ignoreTimeTint"]; sparkle.pipelineData["ignoreTimeTint"] = s.pipelineData["ignoreTimeTint"];
sparkle.setName("sprite-tera-sparkle"); sparkle.setName("sprite-tera-sparkle");
sparkle.play("tera_sparkle"); sparkle.play("tera_sparkle");

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