[Misc][Refactor][GitHub] Ditch eslint for biome, and add a formatter (#5495)
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
7455360824
commit
408b66f913
|
@ -2,92 +2,86 @@
|
|||
module.exports = {
|
||||
forbidden: [
|
||||
{
|
||||
name: 'no-circular-at-runtime',
|
||||
severity: 'warn',
|
||||
name: "no-circular-at-runtime",
|
||||
severity: "warn",
|
||||
comment:
|
||||
'This dependency is part of a circular relationship. You might want to revise ' +
|
||||
'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ',
|
||||
"This dependency is part of a circular relationship. You might want to revise " +
|
||||
"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ",
|
||||
from: {},
|
||||
to: {
|
||||
circular: true,
|
||||
viaOnly: {
|
||||
dependencyTypesNot: [
|
||||
'type-only'
|
||||
]
|
||||
}
|
||||
}
|
||||
dependencyTypesNot: ["type-only"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'no-orphans',
|
||||
name: "no-orphans",
|
||||
comment:
|
||||
"This is an orphan module - it's likely not used (anymore?). Either use it or " +
|
||||
"remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
|
||||
"add an exception for it in your dependency-cruiser configuration. By default " +
|
||||
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
|
||||
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
|
||||
severity: 'warn',
|
||||
severity: "warn",
|
||||
from: {
|
||||
orphan: true,
|
||||
pathNot: [
|
||||
'(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files
|
||||
'[.]d[.]ts$', // TypeScript declaration files
|
||||
'(^|/)tsconfig[.]json$', // TypeScript config
|
||||
'(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs
|
||||
]
|
||||
"(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$", // dot files
|
||||
"[.]d[.]ts$", // TypeScript declaration files
|
||||
"(^|/)tsconfig[.]json$", // TypeScript config
|
||||
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs
|
||||
],
|
||||
},
|
||||
to: {},
|
||||
},
|
||||
{
|
||||
name: 'no-deprecated-core',
|
||||
name: "no-deprecated-core",
|
||||
comment:
|
||||
'A module depends on a node core module that has been deprecated. Find an alternative - these are ' +
|
||||
"A module depends on a node core module that has been deprecated. Find an alternative - these are " +
|
||||
"bound to exist - node doesn't deprecate lightly.",
|
||||
severity: 'warn',
|
||||
severity: "warn",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'core'
|
||||
],
|
||||
dependencyTypes: ["core"],
|
||||
path: [
|
||||
'^v8/tools/codemap$',
|
||||
'^v8/tools/consarray$',
|
||||
'^v8/tools/csvparser$',
|
||||
'^v8/tools/logreader$',
|
||||
'^v8/tools/profile_view$',
|
||||
'^v8/tools/profile$',
|
||||
'^v8/tools/SourceMap$',
|
||||
'^v8/tools/splaytree$',
|
||||
'^v8/tools/tickprocessor-driver$',
|
||||
'^v8/tools/tickprocessor$',
|
||||
'^node-inspect/lib/_inspect$',
|
||||
'^node-inspect/lib/internal/inspect_client$',
|
||||
'^node-inspect/lib/internal/inspect_repl$',
|
||||
'^async_hooks$',
|
||||
'^punycode$',
|
||||
'^domain$',
|
||||
'^constants$',
|
||||
'^sys$',
|
||||
'^_linklist$',
|
||||
'^_stream_wrap$'
|
||||
"^v8/tools/codemap$",
|
||||
"^v8/tools/consarray$",
|
||||
"^v8/tools/csvparser$",
|
||||
"^v8/tools/logreader$",
|
||||
"^v8/tools/profile_view$",
|
||||
"^v8/tools/profile$",
|
||||
"^v8/tools/SourceMap$",
|
||||
"^v8/tools/splaytree$",
|
||||
"^v8/tools/tickprocessor-driver$",
|
||||
"^v8/tools/tickprocessor$",
|
||||
"^node-inspect/lib/_inspect$",
|
||||
"^node-inspect/lib/internal/inspect_client$",
|
||||
"^node-inspect/lib/internal/inspect_repl$",
|
||||
"^async_hooks$",
|
||||
"^punycode$",
|
||||
"^domain$",
|
||||
"^constants$",
|
||||
"^sys$",
|
||||
"^_linklist$",
|
||||
"^_stream_wrap$",
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'not-to-deprecated',
|
||||
name: "not-to-deprecated",
|
||||
comment:
|
||||
'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' +
|
||||
'version of that module, or find an alternative. Deprecated modules are a security risk.',
|
||||
severity: 'warn',
|
||||
"This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " +
|
||||
"version of that module, or find an alternative. Deprecated modules are a security risk.",
|
||||
severity: "warn",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'deprecated'
|
||||
]
|
||||
}
|
||||
dependencyTypes: ["deprecated"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'no-non-package-json',
|
||||
severity: 'error',
|
||||
name: "no-non-package-json",
|
||||
severity: "error",
|
||||
comment:
|
||||
"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
|
||||
"That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
|
||||
|
@ -95,87 +89,75 @@ module.exports = {
|
|||
"in your package.json.",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-no-pkg',
|
||||
'npm-unknown'
|
||||
]
|
||||
}
|
||||
dependencyTypes: ["npm-no-pkg", "npm-unknown"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'not-to-unresolvable',
|
||||
name: "not-to-unresolvable",
|
||||
comment:
|
||||
"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
|
||||
'module: add it to your package.json. In all other cases you likely already know what to do.',
|
||||
severity: 'error',
|
||||
"module: add it to your package.json. In all other cases you likely already know what to do.",
|
||||
severity: "error",
|
||||
from: {},
|
||||
to: {
|
||||
couldNotResolve: true
|
||||
}
|
||||
couldNotResolve: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'no-duplicate-dep-types',
|
||||
name: "no-duplicate-dep-types",
|
||||
comment:
|
||||
"Likely this module depends on an external ('npm') package that occurs more than once " +
|
||||
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
|
||||
"maintenance problems later on.",
|
||||
severity: 'warn',
|
||||
severity: "warn",
|
||||
from: {},
|
||||
to: {
|
||||
moreThanOneDependencyType: true,
|
||||
// as it's pretty common to have a type import be a type only import
|
||||
// as it's pretty common to have a type import be a type only import
|
||||
// _and_ (e.g.) a devDependency - don't consider type-only dependency
|
||||
// types for this rule
|
||||
dependencyTypesNot: ["type-only"]
|
||||
}
|
||||
dependencyTypesNot: ["type-only"],
|
||||
},
|
||||
},
|
||||
|
||||
/* rules you might want to tweak for your specific situation: */
|
||||
|
||||
{
|
||||
name: 'not-to-spec',
|
||||
name: "not-to-spec",
|
||||
comment:
|
||||
'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' +
|
||||
"This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. " +
|
||||
"If there's something in a spec that's of use to other modules, it doesn't have that single " +
|
||||
'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.',
|
||||
severity: 'error',
|
||||
"responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.",
|
||||
severity: "error",
|
||||
from: {},
|
||||
to: {
|
||||
path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
|
||||
}
|
||||
path: "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'not-to-dev-dep',
|
||||
severity: 'error',
|
||||
name: "not-to-dev-dep",
|
||||
severity: "error",
|
||||
comment:
|
||||
"This module depends on an npm package from the 'devDependencies' section of your " +
|
||||
'package.json. It looks like something that ships to production, though. To prevent problems ' +
|
||||
"package.json. It looks like something that ships to production, though. To prevent problems " +
|
||||
"with npm packages that aren't there on production declare it (only!) in the 'dependencies'" +
|
||||
'section of your package.json. If this module is development only - add it to the ' +
|
||||
'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration',
|
||||
"section of your package.json. If this module is development only - add it to the " +
|
||||
"from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration",
|
||||
from: {
|
||||
path: '^(src)',
|
||||
pathNot: [
|
||||
'[.](?:spec|test|setup|script)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$',
|
||||
'./test'
|
||||
]
|
||||
path: "^(src)",
|
||||
pathNot: ["[.](?:spec|test|setup|script)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$", "./test"],
|
||||
},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-dev',
|
||||
],
|
||||
dependencyTypes: ["npm-dev"],
|
||||
// type only dependencies are not a problem as they don't end up in the
|
||||
// production code or are ignored by the runtime.
|
||||
dependencyTypesNot: [
|
||||
'type-only'
|
||||
],
|
||||
pathNot: [
|
||||
'node_modules/@types/'
|
||||
]
|
||||
}
|
||||
dependencyTypesNot: ["type-only"],
|
||||
pathNot: ["node_modules/@types/"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'optional-deps-used',
|
||||
severity: 'info',
|
||||
name: "optional-deps-used",
|
||||
severity: "info",
|
||||
comment:
|
||||
"This module depends on an npm package that is declared as an optional dependency " +
|
||||
"in your package.json. As this makes sense in limited situations only, it's flagged here. " +
|
||||
|
@ -183,33 +165,28 @@ module.exports = {
|
|||
"dependency-cruiser configuration.",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-optional'
|
||||
]
|
||||
}
|
||||
dependencyTypes: ["npm-optional"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'peer-deps-used',
|
||||
name: "peer-deps-used",
|
||||
comment:
|
||||
"This module depends on an npm package that is declared as a peer dependency " +
|
||||
"in your package.json. This makes sense if your package is e.g. a plugin, but in " +
|
||||
"other cases - maybe not so much. If the use of a peer dependency is intentional " +
|
||||
"add an exception to your dependency-cruiser configuration.",
|
||||
severity: 'warn',
|
||||
severity: "warn",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-peer'
|
||||
]
|
||||
}
|
||||
}
|
||||
dependencyTypes: ["npm-peer"],
|
||||
},
|
||||
},
|
||||
],
|
||||
options: {
|
||||
|
||||
/* Which modules not to follow further when encountered */
|
||||
doNotFollow: {
|
||||
/* path: an array of regular expressions in strings to match against */
|
||||
path: ['node_modules']
|
||||
path: ["node_modules"],
|
||||
},
|
||||
|
||||
/* Which modules to exclude */
|
||||
|
@ -271,7 +248,7 @@ module.exports = {
|
|||
defaults to './tsconfig.json'.
|
||||
*/
|
||||
tsConfig: {
|
||||
fileName: 'tsconfig.json'
|
||||
fileName: "tsconfig.json",
|
||||
},
|
||||
|
||||
/* Webpack configuration to use to get resolve options from.
|
||||
|
@ -345,7 +322,7 @@ module.exports = {
|
|||
collapses everything in node_modules to one folder deep so you see
|
||||
the external modules, but their innards.
|
||||
*/
|
||||
collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)',
|
||||
collapsePattern: "node_modules/(?:@[^/]+/[^/]+|[^/]+)",
|
||||
|
||||
/* Options to tweak the appearance of your graph.See
|
||||
https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions
|
||||
|
@ -367,7 +344,8 @@ module.exports = {
|
|||
dependency graph reporter (`archi`) you probably want to tweak
|
||||
this collapsePattern to your situation.
|
||||
*/
|
||||
collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)',
|
||||
collapsePattern:
|
||||
"^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)",
|
||||
|
||||
/* Options to tweak the appearance of your graph. If you don't specify a
|
||||
theme for 'archi' dependency-cruiser will use the one specified in the
|
||||
|
@ -375,10 +353,10 @@ module.exports = {
|
|||
*/
|
||||
// theme: { },
|
||||
},
|
||||
"text": {
|
||||
"highlightFocused": true
|
||||
text: {
|
||||
highlightFocused: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
// generated: dependency-cruiser@16.3.3 on 2024-06-13T23:26:36.169Z
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
name: ESLint
|
||||
name: Biome Code Quality
|
||||
|
||||
on:
|
||||
# Trigger the workflow on push or pull request,
|
||||
|
@ -28,10 +28,13 @@ jobs:
|
|||
- name: Set up Node.js # Step to set up Node.js environment
|
||||
uses: actions/setup-node@v4 # Use the setup-node action version 4
|
||||
with:
|
||||
node-version: 20 # Specify Node.js version 20
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Install Node.js dependencies # Step to install Node.js dependencies
|
||||
run: npm ci # Use 'npm ci' to install dependencies
|
||||
|
||||
|
||||
- name: eslint # Step to run linters
|
||||
run: npm run eslint-ci
|
||||
|
||||
- name: Lint with Biome # Step to run linters
|
||||
run: npm run biome-ci
|
13
README.md
13
README.md
|
@ -3,23 +3,30 @@
|
|||
PokéRogue is a browser based Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, fighting trainers, bosses, and more!
|
||||
|
||||
# Contributing
|
||||
|
||||
## 🛠️ Development
|
||||
|
||||
If you have the motivation and experience with Typescript/Javascript (or are willing to learn) please feel free to fork the repository and make pull requests with contributions. If you don't know what to work on but want to help, reference the below **To-Do** section or the **#feature-vote** channel in the discord.
|
||||
|
||||
### 💻 Environment Setup
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
- node: 20.13.1
|
||||
- npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
|
||||
|
||||
#### Running Locally
|
||||
|
||||
1. Clone the repo and in the root directory run `npm install`
|
||||
- *if you run into any errors, reach out in the **#dev-corner** channel in discord*
|
||||
2. Run `npm run start:dev` to locally run the project in `localhost:8000`
|
||||
|
||||
#### Linting
|
||||
We're using ESLint as our common linter and formatter. It will run automatically during the pre-commit hook but if you would like to manually run it, use the `npm run eslint` script. To view the complete rules, check out the [eslint.config.js](./eslint.config.js) file.
|
||||
|
||||
We're using Biome as our common linter and formatter. It will run automatically during the pre-commit hook but if you would like to manually run it, use the `npm run biome` script. To view the complete rules, check out the [biome.jsonc](./biome.jsonc) file.
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
You can find the auto-generated documentation [here](https://pagefaultgames.github.io/pokerogue/main/index.html).
|
||||
For information on enemy AI, check out the [enemy-ai.md](./docs/enemy-ai.md) file.
|
||||
For detailed guidelines on documenting your code, refer to the [comments.md](./docs/comments.md) file.
|
||||
|
@ -27,16 +34,20 @@ For detailed guidelines on documenting your code, refer to the [comments.md](./d
|
|||
### ❔ FAQ
|
||||
|
||||
**How do I test a new _______?**
|
||||
|
||||
- In the `src/overrides.ts` file there are overrides for most values you'll need to change for testing
|
||||
|
||||
**How do I retrieve the translations?**
|
||||
|
||||
- The translations were moved to the [dedicated translation repository](https://github.com/pagefaultgames/pokerogue-locales) and are now applied as a submodule in this project.
|
||||
- The command to retrieve the translations is `git submodule update --init --recursive`. If you still struggle to get it working, please reach out to #dev-corner channel in Discord.
|
||||
|
||||
## 🪧 To Do
|
||||
|
||||
Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us!
|
||||
|
||||
# 📝 Credits
|
||||
>
|
||||
> If this project contains assets you have produced and you do not see your name, **please** reach out, either [here on GitHub](https://github.com/pagefaultgames/pokerogue/issues/new) or via [Discord](https://discord.gg/pokerogue).
|
||||
|
||||
Thank you to all the wonderful people that have contributed to the PokéRogue project! You can find the credits [here](./CREDITS.md).
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* This script creates a test boilerplate file in the appropriate
|
||||
* This script creates a test boilerplate file in the appropriate
|
||||
* directory based on the type selected.
|
||||
* @example npm run create-test
|
||||
*/
|
||||
|
@ -31,7 +31,8 @@ async function promptTestType() {
|
|||
if (typeAnswer.selectedOption === "EXIT") {
|
||||
console.log("Exiting...");
|
||||
return process.exit();
|
||||
} else if (!typeChoices.includes(typeAnswer.selectedOption)) {
|
||||
}
|
||||
if (!typeChoices.includes(typeAnswer.selectedOption)) {
|
||||
console.error(`Please provide a valid type (${typeChoices.join(", ")})!`);
|
||||
return await promptTestType();
|
||||
}
|
||||
|
@ -74,11 +75,11 @@ async function runInteractive() {
|
|||
const fileName = fileNameAnswer.userInput
|
||||
.replace(/-+/g, "_") // Convert kebab-case (dashes) to underscores
|
||||
.replace(/([a-z])([A-Z])/g, "$1_$2") // Convert camelCase to snake_case
|
||||
.replace(/\s+/g, '_') // Replace spaces with underscores
|
||||
.replace(/\s+/g, "_") // Replace spaces with underscores
|
||||
.toLowerCase(); // Ensure all lowercase
|
||||
// Format the description for the test case
|
||||
|
||||
const formattedName = fileName.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
||||
const formattedName = fileName.replace(/_/g, " ").replace(/\b\w/g, char => char.toUpperCase());
|
||||
// Determine the directory based on the type
|
||||
let dir;
|
||||
let description;
|
||||
|
|
|
@ -10,4 +10,4 @@ for await (const chunk of process.stdin) {
|
|||
const file = Buffer.concat(inputFile).toString("utf-8");
|
||||
|
||||
const svg = graphviz.dot(file, "svg");
|
||||
process.stdout.write(svg);
|
||||
process.stdout.write(svg);
|
||||
|
|
109
eslint.config.js
109
eslint.config.js
|
@ -1,70 +1,43 @@
|
|||
import tseslint from '@typescript-eslint/eslint-plugin';
|
||||
import stylisticTs from '@stylistic/eslint-plugin-ts';
|
||||
import parser from '@typescript-eslint/parser';
|
||||
import importX from 'eslint-plugin-import-x';
|
||||
import tseslint from "@typescript-eslint/eslint-plugin";
|
||||
import stylisticTs from "@stylistic/eslint-plugin-ts";
|
||||
import parser from "@typescript-eslint/parser";
|
||||
import importX from "eslint-plugin-import-x";
|
||||
|
||||
export default [
|
||||
{
|
||||
name: "eslint-config",
|
||||
files: ["src/**/*.{ts,tsx,js,jsx}", "test/**/*.{ts,tsx,js,jsx}"],
|
||||
ignores: ["dist/*", "build/*", "coverage/*", "public/*", ".github/*", "node_modules/*", ".vscode/*"],
|
||||
languageOptions: {
|
||||
parser: parser
|
||||
},
|
||||
plugins: {
|
||||
"import-x": importX,
|
||||
'@stylistic/ts': stylisticTs,
|
||||
'@typescript-eslint': tseslint
|
||||
},
|
||||
rules: {
|
||||
"eqeqeq": ["error", "always"], // Enforces the use of `===` and `!==` instead of `==` and `!=`
|
||||
"indent": ["error", 2, { "SwitchCase": 1 }], // Enforces a 2-space indentation, enforces indentation of `case ...:` statements
|
||||
"quotes": ["error", "double"], // Enforces the use of double quotes for strings
|
||||
"no-var": "error", // Disallows the use of `var`, enforcing `let` or `const` instead
|
||||
"prefer-const": "error", // Enforces the use of `const` for variables that are never reassigned
|
||||
"no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this)
|
||||
"@typescript-eslint/no-unused-vars": [ "error", {
|
||||
"args": "none", // Allows unused function parameters. Useful for functions with specific signatures where not all parameters are always used.
|
||||
"ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the others.
|
||||
}],
|
||||
"eol-last": ["error", "always"], // Enforces at least one newline at the end of files
|
||||
"@stylistic/ts/semi": ["error", "always"], // Requires semicolons for TypeScript-specific syntax
|
||||
"semi": "off", // Disables the general semi rule for TypeScript files
|
||||
"no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax
|
||||
"brace-style": "off", // Note: you must disable the base rule as it can report incorrect errors
|
||||
"curly": ["error", "all"], // Enforces the use of curly braces for all control statements
|
||||
"@stylistic/ts/brace-style": ["error", "1tbs"], // Enforces the following brace style: https://eslint.style/rules/js/brace-style#_1tbs
|
||||
"no-trailing-spaces": ["error", { // Disallows trailing whitespace at the end of lines
|
||||
"skipBlankLines": false, // Enforces the rule even on blank lines
|
||||
"ignoreComments": false // Enforces the rule on lines containing comments
|
||||
}],
|
||||
"space-before-blocks": ["error", "always"], // Enforces a space before blocks
|
||||
"keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords
|
||||
"comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after commas
|
||||
"import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json
|
||||
"array-bracket-spacing": ["error", "always", { "objectsInArrays": false, "arraysInArrays": false }], // Enforces consistent spacing inside array brackets
|
||||
"object-curly-spacing": ["error", "always", { "arraysInObjects": false, "objectsInObjects": false }], // Enforces consistent spacing inside braces of object literals, destructuring assignments, and import/export specifiers
|
||||
"computed-property-spacing": ["error", "never" ], // Enforces consistent spacing inside computed property brackets
|
||||
"space-infix-ops": ["error", { "int32Hint": false }], // Enforces spacing around infix operators
|
||||
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], // Disallows multiple empty lines
|
||||
"@typescript-eslint/consistent-type-imports": "error", // Enforces type-only imports wherever possible
|
||||
}
|
||||
export default [
|
||||
{
|
||||
name: "eslint-config",
|
||||
files: ["src/**/*.{ts,tsx,js,jsx}", "test/**/*.{ts,tsx,js,jsx}"],
|
||||
ignores: ["dist/*", "build/*", "coverage/*", "public/*", ".github/*", "node_modules/*", ".vscode/*"],
|
||||
languageOptions: {
|
||||
parser: parser,
|
||||
},
|
||||
{
|
||||
name: "eslint-tests",
|
||||
files: ["test/**/**.test.ts"],
|
||||
languageOptions: {
|
||||
parser: parser,
|
||||
parserOptions: {
|
||||
"project": ["./tsconfig.json"]
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": tseslint
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-floating-promises": "error", // Require Promise-like statements to be handled appropriately. - https://typescript-eslint.io/rules/no-floating-promises/
|
||||
"@typescript-eslint/no-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/
|
||||
}
|
||||
}
|
||||
]
|
||||
plugins: {
|
||||
"import-x": importX,
|
||||
"@stylistic/ts": stylisticTs,
|
||||
"@typescript-eslint": tseslint,
|
||||
},
|
||||
rules: {
|
||||
"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-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax
|
||||
"import-x/extensions": ["error", "never", { json: "always" }], // Enforces no extension for imports unless json
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "eslint-tests",
|
||||
files: ["test/**/**.test.ts"],
|
||||
languageOptions: {
|
||||
parser: parser,
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"],
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": tseslint,
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-floating-promises": "error", // Require Promise-like statements to be handled appropriately. - https://typescript-eslint.io/rules/no-floating-promises/
|
||||
"@typescript-eslint/no-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
190
index.css
190
index.css
|
@ -11,7 +11,7 @@ html {
|
|||
|
||||
body {
|
||||
margin: 0;
|
||||
display:flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: #484050;
|
||||
|
@ -49,16 +49,17 @@ body {
|
|||
|
||||
@media (pointer: coarse) {
|
||||
/* hasTouchscreen() && !isTouchControlsEnabled */
|
||||
body:has(> #touchControls[class=visible]) #app {
|
||||
body:has(> #touchControls[class="visible"]) #app {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
body:has(> #touchControls[class=visible]) #app > div:first-child {
|
||||
body:has(> #touchControls[class="visible"]) #app > div:first-child {
|
||||
transform-origin: top !important;
|
||||
}
|
||||
}
|
||||
|
||||
#layout:fullscreen #dpad, #layout:fullscreen {
|
||||
#layout:fullscreen #dpad,
|
||||
#layout:fullscreen {
|
||||
bottom: 6rem;
|
||||
}
|
||||
|
||||
|
@ -76,7 +77,6 @@ input {
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
input:-internal-autofill-selected {
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
|
@ -91,18 +91,33 @@ input:-internal-autofill-selected {
|
|||
|
||||
--controls-padding: 1rem;
|
||||
|
||||
--controls-size-with-padding: calc(var(--controls-size) + var(--controls-padding));
|
||||
--controls-size-with-wide-padding: calc(var(--controls-size) *1.2 + var(--controls-padding));
|
||||
--controls-size-with-padding: calc(
|
||||
var(--controls-size) +
|
||||
var(--controls-padding)
|
||||
);
|
||||
--controls-size-with-wide-padding: calc(
|
||||
var(--controls-size) *
|
||||
1.2 +
|
||||
var(--controls-padding)
|
||||
);
|
||||
--control-group-extra-size: calc(var(--controls-size) * 0.8);
|
||||
--control-group-extra-wide-size: calc(var(--controls-size) * 1.2);
|
||||
|
||||
--control-group-extra-2-offset: calc(var(--controls-size-with-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2);
|
||||
--control-group-extra-1-offset: calc(var(--controls-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2);
|
||||
--control-group-extra-2-offset: calc(
|
||||
var(--controls-size-with-padding) +
|
||||
(var(--controls-size) - var(--control-group-extra-size)) /
|
||||
2
|
||||
);
|
||||
--control-group-extra-1-offset: calc(
|
||||
var(--controls-padding) +
|
||||
(var(--controls-size) - var(--control-group-extra-size)) /
|
||||
2
|
||||
);
|
||||
|
||||
--small-control-size: calc(var(--controls-size) / 3);
|
||||
--rect-control-size: calc(var(--controls-size) * 0.74);
|
||||
|
||||
font-family: 'emerald';
|
||||
font-family: "emerald";
|
||||
font-size: var(--controls-size);
|
||||
text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size);
|
||||
color: var(--color-light);
|
||||
|
@ -146,32 +161,69 @@ input:-internal-autofill-selected {
|
|||
/* Hide buttons on specific UIs */
|
||||
|
||||
/* Show #apadPreviousTab and #apadNextTab only in settings, except in touch configuration panel */
|
||||
#touchControls:not([data-ui-mode^='SETTINGS']) #apadPreviousTab,
|
||||
#touchControls:not([data-ui-mode^='SETTINGS']) #apadNextTab,
|
||||
#touchControls:not([data-ui-mode^="SETTINGS"]) #apadPreviousTab,
|
||||
#touchControls:not([data-ui-mode^="SETTINGS"]) #apadNextTab,
|
||||
#touchControls:is(.config-mode) #apadPreviousTab,
|
||||
#touchControls:is(.config-mode) #apadNextTab {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show #apadInfo only in battle */
|
||||
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']) #apadInfo {
|
||||
#touchControls:not([data-ui-mode="COMMAND"]):not([data-ui-mode="FIGHT"]):not(
|
||||
[data-ui-mode="BALL"]
|
||||
):not([data-ui-mode="TARGET_SELECT"])
|
||||
#apadInfo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show #apadStats only in battle and shop */
|
||||
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']):not([data-ui-mode='MODIFIER_SELECT']) #apadStats {
|
||||
#touchControls:not([data-ui-mode="COMMAND"]):not([data-ui-mode="FIGHT"]):not(
|
||||
[data-ui-mode="BALL"]
|
||||
):not([data-ui-mode="TARGET_SELECT"]):not([data-ui-mode="MODIFIER_SELECT"])
|
||||
#apadStats {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show cycle buttons only on STARTER_SELECT and on touch configuration panel */
|
||||
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX'], [data-ui-mode='POKEDEX_PAGE']) #apadOpenFilters,
|
||||
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX'], [data-ui-mode='POKEDEX_PAGE'], [data-ui-mode='RUN_INFO']) #apadCycleForm,
|
||||
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX'], [data-ui-mode='POKEDEX_PAGE'], [data-ui-mode='RUN_INFO']) #apadCycleShiny,
|
||||
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleNature,
|
||||
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX_PAGE'], [data-ui-mode='RUN_INFO']) #apadCycleAbility,
|
||||
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX_PAGE']) #apadCycleGender,
|
||||
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX']) #apadCycleTera {
|
||||
display: none;
|
||||
#touchControls:not(.config-mode):not(
|
||||
[data-ui-mode="STARTER_SELECT"],
|
||||
[data-ui-mode="POKEDEX"],
|
||||
[data-ui-mode="POKEDEX_PAGE"]
|
||||
)
|
||||
#apadOpenFilters,
|
||||
#touchControls:not(.config-mode):not(
|
||||
[data-ui-mode="STARTER_SELECT"],
|
||||
[data-ui-mode="POKEDEX"],
|
||||
[data-ui-mode="POKEDEX_PAGE"],
|
||||
[data-ui-mode="RUN_INFO"]
|
||||
)
|
||||
#apadCycleForm,
|
||||
#touchControls:not(.config-mode):not(
|
||||
[data-ui-mode="STARTER_SELECT"],
|
||||
[data-ui-mode="POKEDEX"],
|
||||
[data-ui-mode="POKEDEX_PAGE"],
|
||||
[data-ui-mode="RUN_INFO"]
|
||||
)
|
||||
#apadCycleShiny,
|
||||
#touchControls:not(.config-mode):not([data-ui-mode="STARTER_SELECT"])
|
||||
#apadCycleNature,
|
||||
#touchControls:not(.config-mode):not(
|
||||
[data-ui-mode="STARTER_SELECT"],
|
||||
[data-ui-mode="POKEDEX_PAGE"],
|
||||
[data-ui-mode="RUN_INFO"]
|
||||
)
|
||||
#apadCycleAbility,
|
||||
#touchControls:not(.config-mode):not(
|
||||
[data-ui-mode="STARTER_SELECT"],
|
||||
[data-ui-mode="POKEDEX_PAGE"]
|
||||
)
|
||||
#apadCycleGender,
|
||||
#touchControls:not(.config-mode):not(
|
||||
[data-ui-mode="STARTER_SELECT"],
|
||||
[data-ui-mode="POKEDEX"]
|
||||
)
|
||||
#apadCycleTera {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Configuration toolbar */
|
||||
|
@ -217,16 +269,18 @@ input:-internal-autofill-selected {
|
|||
font-size: var(--small-control-size);
|
||||
border-radius: 8px;
|
||||
padding: 2px 8px;
|
||||
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
|
||||
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3)
|
||||
calc(var(--text-shadow-size) / 3);
|
||||
}
|
||||
|
||||
#configToolbar .button:active {
|
||||
opacity: var(--touch-control-opacity)
|
||||
opacity: var(--touch-control-opacity);
|
||||
}
|
||||
|
||||
#configToolbar .orientation-label {
|
||||
font-size: var(--small-control-size);
|
||||
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
|
||||
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3)
|
||||
calc(var(--text-shadow-size) / 3);
|
||||
}
|
||||
|
||||
/* dpad */
|
||||
|
@ -270,7 +324,8 @@ input:-internal-autofill-selected {
|
|||
|
||||
.apad-small > .apad-label {
|
||||
font-size: var(--small-control-size);
|
||||
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3);
|
||||
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3)
|
||||
calc(var(--text-shadow-size) / 3);
|
||||
}
|
||||
|
||||
.apad-rectangle {
|
||||
|
@ -320,7 +375,8 @@ input:-internal-autofill-selected {
|
|||
|
||||
/* Layout */
|
||||
|
||||
#layout:fullscreen #dpad, #layout:fullscreen #apad {
|
||||
#layout:fullscreen #dpad,
|
||||
#layout:fullscreen #apad {
|
||||
bottom: 6rem;
|
||||
}
|
||||
|
||||
|
@ -353,55 +409,55 @@ a {
|
|||
|
||||
/* Firefox old*/
|
||||
@-moz-keyframes blink {
|
||||
0% {
|
||||
opacity:1;
|
||||
}
|
||||
50% {
|
||||
opacity:0;
|
||||
}
|
||||
100% {
|
||||
opacity:1;
|
||||
}
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes blink {
|
||||
0% {
|
||||
opacity:1;
|
||||
}
|
||||
50% {
|
||||
opacity:0;
|
||||
}
|
||||
100% {
|
||||
opacity:1;
|
||||
}
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
/* IE */
|
||||
@-ms-keyframes blink {
|
||||
0% {
|
||||
opacity:1;
|
||||
}
|
||||
50% {
|
||||
opacity:0;
|
||||
}
|
||||
100% {
|
||||
opacity:1;
|
||||
}
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
/* Opera and prob css3 final iteration */
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity:1;
|
||||
}
|
||||
50% {
|
||||
opacity:0;
|
||||
}
|
||||
100% {
|
||||
opacity:1;
|
||||
}
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.blink-image {
|
||||
-moz-animation: blink normal 4s infinite ease-in-out; /* Firefox */
|
||||
-webkit-animation: blink normal 4s infinite ease-in-out; /* Webkit */
|
||||
-ms-animation: blink normal 4s infinite ease-in-out; /* IE */
|
||||
animation: blink normal 4s infinite ease-in-out; /* Opera and prob css3 final iteration */
|
||||
-moz-animation: blink normal 4s infinite ease-in-out; /* Firefox */
|
||||
-webkit-animation: blink normal 4s infinite ease-in-out; /* Webkit */
|
||||
-ms-animation: blink normal 4s infinite ease-in-out; /* IE */
|
||||
animation: blink normal 4s infinite ease-in-out; /* Opera and prob css3 final iteration */
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
pre-commit:
|
||||
parallel: true
|
||||
commands:
|
||||
eslint:
|
||||
biome-lint:
|
||||
glob: "*.{js,jsx,ts,tsx}"
|
||||
run: npx eslint --fix {staged_files}
|
||||
run: npx @biomejs/biome check --write --reporter=summary {staged_files} --no-errors-on-unmatched
|
||||
stage_fixed: true
|
||||
skip:
|
||||
- merge
|
||||
|
@ -11,9 +11,9 @@ pre-commit:
|
|||
|
||||
pre-push:
|
||||
commands:
|
||||
eslint:
|
||||
biome-lint:
|
||||
glob: "*.{js,ts,jsx,tsx}"
|
||||
run: npx eslint --fix {push_files}
|
||||
run: npx @biomejs/biome check --write --reporter=summary {push_files} --no-errors-on-unmatched
|
||||
|
||||
post-merge:
|
||||
commands:
|
||||
|
|
File diff suppressed because it is too large
Load Diff
135
package.json
135
package.json
|
@ -1,68 +1,71 @@
|
|||
{
|
||||
"name": "pokemon-rogue-battle",
|
||||
"private": true,
|
||||
"version": "1.7.7",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"start:dev": "vite --mode development",
|
||||
"build": "vite build",
|
||||
"build:beta": "vite build --mode beta",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run --project pre && vitest run --project main",
|
||||
"test:cov": "vitest run --project pre && vitest run --project main --coverage",
|
||||
"test:watch": "vitest run --project pre && vitest watch --project main --coverage",
|
||||
"test:silent": "vitest run --project pre && vitest run --project main --silent",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"eslint": "eslint --fix .",
|
||||
"eslint-ci": "eslint .",
|
||||
"docs": "typedoc",
|
||||
"depcruise": "depcruise src",
|
||||
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
|
||||
"create-test": "node ./create-test-boilerplate.js",
|
||||
"postinstall": "npx lefthook install && npx lefthook run post-merge",
|
||||
"update-version:patch": "npm version patch --force --no-git-tag-version",
|
||||
"update-version:minor": "npm version minor --force --no-git-tag-version",
|
||||
"update-locales:remote": "git submodule update --progress --init --recursive --force --remote"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.3.0",
|
||||
"@hpcc-js/wasm": "^2.18.0",
|
||||
"@stylistic/eslint-plugin-ts": "^2.6.0-beta.0",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^20.12.13",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0-alpha.54",
|
||||
"@typescript-eslint/parser": "^8.0.0-alpha.54",
|
||||
"@vitest/coverage-istanbul": "^2.1.9",
|
||||
"dependency-cruiser": "^16.3.10",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-plugin-import-x": "^4.2.1",
|
||||
"inquirer": "^11.0.2",
|
||||
"jsdom": "^24.0.0",
|
||||
"lefthook": "^1.6.12",
|
||||
"msw": "^2.4.9",
|
||||
"phaser3spectorjs": "^0.0.8",
|
||||
"typedoc": "^0.26.4",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.0.0-alpha.54",
|
||||
"vite": "^5.4.14",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^2.1.9",
|
||||
"vitest-canvas-mock": "^0.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material/material-color-utilities": "^0.2.7",
|
||||
"crypto-js": "^4.2.0",
|
||||
"i18next": "^23.11.1",
|
||||
"i18next-browser-languagedetector": "^7.2.1",
|
||||
"i18next-http-backend": "^2.6.1",
|
||||
"i18next-korean-postposition-processor": "^1.0.0",
|
||||
"json-stable-stringify": "^1.1.0",
|
||||
"jszip": "^3.10.1",
|
||||
"phaser": "^3.70.0",
|
||||
"phaser3-rex-plugins": "^1.1.84"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
"name": "pokemon-rogue-battle",
|
||||
"private": true,
|
||||
"version": "1.7.7",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"start:dev": "vite --mode development",
|
||||
"build": "vite build",
|
||||
"build:beta": "vite build --mode beta",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run --project pre && vitest run --project main",
|
||||
"test:cov": "vitest run --project pre && vitest run --project main --coverage",
|
||||
"test:watch": "vitest run --project pre && vitest watch --project main --coverage",
|
||||
"test:silent": "vitest run --project pre && vitest run --project main --silent",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"eslint": "eslint --fix .",
|
||||
"eslint-ci": "eslint .",
|
||||
"biome": "biome check --write --changed --no-errors-on-unmatched",
|
||||
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --changed --no-errors-on-unmatched",
|
||||
"docs": "typedoc",
|
||||
"depcruise": "depcruise src",
|
||||
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
|
||||
"create-test": "node ./create-test-boilerplate.js",
|
||||
"postinstall": "npx lefthook install && npx lefthook run post-merge",
|
||||
"update-version:patch": "npm version patch --force --no-git-tag-version",
|
||||
"update-version:minor": "npm version minor --force --no-git-tag-version",
|
||||
"update-locales:remote": "git submodule update --progress --init --recursive --force --remote"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@eslint/js": "^9.3.0",
|
||||
"@hpcc-js/wasm": "^2.18.0",
|
||||
"@stylistic/eslint-plugin-ts": "^2.6.0-beta.0",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^20.12.13",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0-alpha.54",
|
||||
"@typescript-eslint/parser": "^8.0.0-alpha.54",
|
||||
"@vitest/coverage-istanbul": "^2.1.9",
|
||||
"dependency-cruiser": "^16.3.10",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-plugin-import-x": "^4.2.1",
|
||||
"inquirer": "^11.0.2",
|
||||
"jsdom": "^24.0.0",
|
||||
"lefthook": "^1.6.12",
|
||||
"msw": "^2.4.9",
|
||||
"phaser3spectorjs": "^0.0.8",
|
||||
"typedoc": "^0.26.4",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.0.0-alpha.54",
|
||||
"vite": "^5.4.14",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^2.1.9",
|
||||
"vitest-canvas-mock": "^0.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material/material-color-utilities": "^0.2.7",
|
||||
"crypto-js": "^4.2.0",
|
||||
"i18next": "^23.11.1",
|
||||
"i18next-browser-languagedetector": "^7.2.1",
|
||||
"i18next-http-backend": "^2.6.1",
|
||||
"i18next-korean-postposition-processor": "^1.0.0",
|
||||
"json-stable-stringify": "^1.1.0",
|
||||
"jszip": "^3.10.1",
|
||||
"phaser": "^3.70.0",
|
||||
"phaser3-rex-plugins": "^1.1.84"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,25 @@ export let loggedInUser: UserInfo | null = null;
|
|||
export const clientSessionId = Utils.randomString(32);
|
||||
|
||||
export function initLoggedInUser(): void {
|
||||
loggedInUser = { username: "Guest", lastSessionSlot: -1, discordId: "", googleId: "", hasAdminRole: false };
|
||||
loggedInUser = {
|
||||
username: "Guest",
|
||||
lastSessionSlot: -1,
|
||||
discordId: "",
|
||||
googleId: "",
|
||||
hasAdminRole: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateUserInfo(): Promise<[boolean, number]> {
|
||||
return new Promise<[boolean, number]>(resolve => {
|
||||
if (bypassLogin) {
|
||||
loggedInUser = { username: "Guest", lastSessionSlot: -1, discordId: "", googleId: "", hasAdminRole: false };
|
||||
loggedInUser = {
|
||||
username: "Guest",
|
||||
lastSessionSlot: -1,
|
||||
discordId: "",
|
||||
googleId: "",
|
||||
hasAdminRole: false,
|
||||
};
|
||||
let lastSessionSlot = -1;
|
||||
for (let s = 0; s < 5; s++) {
|
||||
if (localStorage.getItem(`sessionData${s ? s : ""}_${loggedInUser.username}`)) {
|
||||
|
@ -24,7 +36,7 @@ export function updateUserInfo(): Promise<[boolean, number]> {
|
|||
}
|
||||
loggedInUser.lastSessionSlot = lastSessionSlot;
|
||||
// Migrate old data from before the username was appended
|
||||
[ "data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4" ].map(d => {
|
||||
["data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4"].map(d => {
|
||||
const lsItem = localStorage.getItem(d);
|
||||
if (lsItem && !!loggedInUser?.username) {
|
||||
const lsUserItem = localStorage.getItem(`${d}_${loggedInUser.username}`);
|
||||
|
@ -35,16 +47,15 @@ export function updateUserInfo(): Promise<[boolean, number]> {
|
|||
localStorage.removeItem(d);
|
||||
}
|
||||
});
|
||||
return resolve([ true, 200 ]);
|
||||
return resolve([true, 200]);
|
||||
}
|
||||
pokerogueApi.account.getInfo().then(([ accountInfo, status ]) => {
|
||||
pokerogueApi.account.getInfo().then(([accountInfo, status]) => {
|
||||
if (!accountInfo) {
|
||||
resolve([ false, status ]);
|
||||
resolve([false, status]);
|
||||
return;
|
||||
} else {
|
||||
loggedInUser = accountInfo;
|
||||
resolve([ true, 200 ]);
|
||||
}
|
||||
loggedInUser = accountInfo;
|
||||
resolve([true, 200]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
1335
src/battle-scene.ts
1335
src/battle-scene.ts
File diff suppressed because it is too large
Load Diff
539
src/battle.ts
539
src/battle.ts
|
@ -50,7 +50,7 @@ export enum BattleType {
|
|||
WILD,
|
||||
TRAINER,
|
||||
CLEAR,
|
||||
MYSTERY_ENCOUNTER
|
||||
MYSTERY_ENCOUNTER,
|
||||
}
|
||||
|
||||
export enum BattlerIndex {
|
||||
|
@ -58,25 +58,25 @@ export enum BattlerIndex {
|
|||
PLAYER,
|
||||
PLAYER_2,
|
||||
ENEMY,
|
||||
ENEMY_2
|
||||
ENEMY_2,
|
||||
}
|
||||
|
||||
export interface TurnCommand {
|
||||
command: Command;
|
||||
cursor?: number;
|
||||
move?: TurnMove;
|
||||
targets?: BattlerIndex[];
|
||||
skip?: boolean;
|
||||
args?: any[];
|
||||
command: Command;
|
||||
cursor?: number;
|
||||
move?: TurnMove;
|
||||
targets?: BattlerIndex[];
|
||||
skip?: boolean;
|
||||
args?: any[];
|
||||
}
|
||||
|
||||
export interface FaintLogEntry {
|
||||
pokemon: Pokemon,
|
||||
turn: number
|
||||
pokemon: Pokemon;
|
||||
turn: number;
|
||||
}
|
||||
|
||||
interface TurnCommands {
|
||||
[key: number]: TurnCommand | null
|
||||
[key: number]: TurnCommand | null;
|
||||
}
|
||||
|
||||
export default class Battle {
|
||||
|
@ -89,19 +89,19 @@ export default class Battle {
|
|||
public enemyParty: EnemyPokemon[] = [];
|
||||
public seenEnemyPartyMemberIds: Set<number> = new Set<number>();
|
||||
public double: boolean;
|
||||
public started: boolean = false;
|
||||
public enemySwitchCounter: number = 0;
|
||||
public turn: number = 0;
|
||||
public started = false;
|
||||
public enemySwitchCounter = 0;
|
||||
public turn = 0;
|
||||
public preTurnCommands: TurnCommands;
|
||||
public turnCommands: TurnCommands;
|
||||
public playerParticipantIds: Set<number> = new Set<number>();
|
||||
public battleScore: number = 0;
|
||||
public battleScore = 0;
|
||||
public postBattleLoot: PokemonHeldItemModifier[] = [];
|
||||
public escapeAttempts: number = 0;
|
||||
public escapeAttempts = 0;
|
||||
public lastMove: Moves;
|
||||
public battleSeed: string = Utils.randomString(16, true);
|
||||
private battleSeedState: string | null = null;
|
||||
public moneyScattered: number = 0;
|
||||
public moneyScattered = 0;
|
||||
/** Primarily for double battles, keeps track of last enemy and player pokemon that triggered its ability or used a move */
|
||||
public lastEnemyInvolved: number;
|
||||
public lastPlayerInvolved: number;
|
||||
|
@ -111,7 +111,7 @@ export default class Battle {
|
|||
* This is saved here since we encounter a new enemy every wave.
|
||||
* {@linkcode globalScene.arena.playerFaints} is the corresponding faint counter for the player and needs to be save across waves (reset every arena encounter).
|
||||
*/
|
||||
public enemyFaints: number = 0;
|
||||
public enemyFaints = 0;
|
||||
public playerFaintsHistory: FaintLogEntry[] = [];
|
||||
public enemyFaintsHistory: FaintLogEntry[] = [];
|
||||
|
||||
|
@ -119,17 +119,18 @@ export default class Battle {
|
|||
/** If the current battle is a Mystery Encounter, this will always be defined */
|
||||
public mysteryEncounter?: MysteryEncounter;
|
||||
|
||||
private rngCounter: number = 0;
|
||||
private rngCounter = 0;
|
||||
|
||||
constructor(gameMode: GameMode, waveIndex: number, battleType: BattleType, trainer?: Trainer, double: boolean = false) {
|
||||
constructor(gameMode: GameMode, waveIndex: number, battleType: BattleType, trainer?: Trainer, double = false) {
|
||||
this.gameMode = gameMode;
|
||||
this.waveIndex = waveIndex;
|
||||
this.battleType = battleType;
|
||||
this.trainer = trainer ?? null;
|
||||
this.initBattleSpec();
|
||||
this.enemyLevels = battleType !== BattleType.TRAINER
|
||||
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
|
||||
: trainer?.getPartyLevels(this.waveIndex);
|
||||
this.enemyLevels =
|
||||
battleType !== BattleType.TRAINER
|
||||
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
|
||||
: trainer?.getPartyLevels(this.waveIndex);
|
||||
this.double = double;
|
||||
}
|
||||
|
||||
|
@ -180,8 +181,8 @@ export default class Battle {
|
|||
|
||||
incrementTurn(): void {
|
||||
this.turn++;
|
||||
this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ]));
|
||||
this.preTurnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ]));
|
||||
this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [bt, null]));
|
||||
this.preTurnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [bt, null]));
|
||||
this.battleSeedState = null;
|
||||
}
|
||||
|
||||
|
@ -194,12 +195,19 @@ export default class Battle {
|
|||
}
|
||||
|
||||
addPostBattleLoot(enemyPokemon: EnemyPokemon): void {
|
||||
this.postBattleLoot.push(...globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, false).map(i => {
|
||||
const ret = i as PokemonHeldItemModifier;
|
||||
//@ts-ignore - this is awful to fix/change
|
||||
ret.pokemonId = null;
|
||||
return ret;
|
||||
}));
|
||||
this.postBattleLoot.push(
|
||||
...globalScene
|
||||
.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable,
|
||||
false,
|
||||
)
|
||||
.map(i => {
|
||||
const ret = i as PokemonHeldItemModifier;
|
||||
//@ts-ignore - this is awful to fix/change
|
||||
ret.pokemonId = null;
|
||||
return ret;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
pickUpScatteredMoney(): void {
|
||||
|
@ -214,7 +222,9 @@ export default class Battle {
|
|||
|
||||
const userLocale = navigator.language || "en-US";
|
||||
const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale);
|
||||
const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount });
|
||||
const message = i18next.t("battle:moneyPickedUp", {
|
||||
moneyAmount: formattedMoneyAmount,
|
||||
});
|
||||
globalScene.queueMessage(message, undefined, true);
|
||||
|
||||
globalScene.currentBattle.moneyScattered = 0;
|
||||
|
@ -227,13 +237,17 @@ export default class Battle {
|
|||
}
|
||||
for (const p of globalScene.getEnemyParty()) {
|
||||
if (p.isBoss()) {
|
||||
partyMemberTurnMultiplier *= (p.bossSegments / 1.5) / globalScene.getEnemyParty().length;
|
||||
partyMemberTurnMultiplier *= p.bossSegments / 1.5 / globalScene.getEnemyParty().length;
|
||||
}
|
||||
}
|
||||
const turnMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")(1 - Math.min(this.turn - 2, 10 * partyMemberTurnMultiplier) / (10 * partyMemberTurnMultiplier));
|
||||
const turnMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")(
|
||||
1 - Math.min(this.turn - 2, 10 * partyMemberTurnMultiplier) / (10 * partyMemberTurnMultiplier),
|
||||
);
|
||||
const finalBattleScore = Math.ceil(this.battleScore * turnMultiplier);
|
||||
globalScene.score += finalBattleScore;
|
||||
console.log(`Battle Score: ${finalBattleScore} (${this.turn - 1} Turns x${Math.floor(turnMultiplier * 100) / 100})`);
|
||||
console.log(
|
||||
`Battle Score: ${finalBattleScore} (${this.turn - 1} Turns x${Math.floor(turnMultiplier * 100) / 100})`,
|
||||
);
|
||||
console.log(`Total Score: ${globalScene.score}`);
|
||||
globalScene.updateScoreText();
|
||||
}
|
||||
|
@ -243,16 +257,20 @@ export default class Battle {
|
|||
// Music is overridden for MEs during ME onInit()
|
||||
// Should not use any BGM overrides before swapping from DEFAULT mode
|
||||
return null;
|
||||
} else if (this.battleType === BattleType.TRAINER || this.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
|
||||
}
|
||||
if (
|
||||
this.battleType === BattleType.TRAINER ||
|
||||
this.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE
|
||||
) {
|
||||
if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) {
|
||||
return `encounter_${this.trainer?.getEncounterBgm()}`;
|
||||
}
|
||||
if (globalScene.musicPreference === MusicPreference.GENFIVE) {
|
||||
return this.trainer?.getBattleBgm() ?? null;
|
||||
} else {
|
||||
return this.trainer?.getMixedBattleBgm() ?? null;
|
||||
}
|
||||
} else if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) {
|
||||
return this.trainer?.getMixedBattleBgm() ?? null;
|
||||
}
|
||||
if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) {
|
||||
return "end_summit";
|
||||
}
|
||||
const wildOpponents = globalScene.getEnemyParty();
|
||||
|
@ -281,7 +299,8 @@ export default class Battle {
|
|||
}
|
||||
return "battle_legendary_unova";
|
||||
}
|
||||
} else if (globalScene.musicPreference === MusicPreference.ALLGENS) {
|
||||
}
|
||||
if (globalScene.musicPreference === MusicPreference.ALLGENS) {
|
||||
switch (pokemon.species.speciesId) {
|
||||
case Species.ARTICUNO:
|
||||
case Species.ZAPDOS:
|
||||
|
@ -434,7 +453,7 @@ export default class Battle {
|
|||
* @param min The minimum integer to pick, default `0`
|
||||
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
|
||||
*/
|
||||
randSeedInt(range: number, min: number = 0): number {
|
||||
randSeedInt(range: number, min = 0): number {
|
||||
if (range <= 1) {
|
||||
return min;
|
||||
}
|
||||
|
@ -444,7 +463,7 @@ export default class Battle {
|
|||
if (this.battleSeedState) {
|
||||
Phaser.Math.RND.state(this.battleSeedState);
|
||||
} else {
|
||||
Phaser.Math.RND.sow([ Utils.shiftCharCodes(this.battleSeed, this.turn << 6) ]);
|
||||
Phaser.Math.RND.sow([Utils.shiftCharCodes(this.battleSeed, this.turn << 6)]);
|
||||
console.log("Battle Seed:", this.battleSeed);
|
||||
}
|
||||
globalScene.rngCounter = this.rngCounter++;
|
||||
|
@ -467,7 +486,13 @@ export default class Battle {
|
|||
|
||||
export class FixedBattle extends Battle {
|
||||
constructor(waveIndex: number, config: FixedBattleConfig) {
|
||||
super(globalScene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer() : undefined, config.double);
|
||||
super(
|
||||
globalScene.gameMode,
|
||||
waveIndex,
|
||||
config.battleType,
|
||||
config.battleType === BattleType.TRAINER ? config.getTrainer() : undefined,
|
||||
config.double,
|
||||
);
|
||||
if (config.getEnemyParty) {
|
||||
this.enemyParty = config.getEnemyParty();
|
||||
}
|
||||
|
@ -516,7 +541,6 @@ export class FixedBattleConfig {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to generate a random trainer for evil team trainers and the elite 4/champion
|
||||
* @param trainerPool The TrainerType or list of TrainerTypes that can possibly be generated
|
||||
|
@ -524,31 +548,44 @@ export class FixedBattleConfig {
|
|||
* @param seedOffset the seed offset to use for the random generation of the trainer
|
||||
* @returns the generated trainer
|
||||
*/
|
||||
export function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], randomGender: boolean = false, seedOffset: number = 0): GetTrainerFunc {
|
||||
export function getRandomTrainerFunc(
|
||||
trainerPool: (TrainerType | TrainerType[])[],
|
||||
randomGender = false,
|
||||
seedOffset = 0,
|
||||
): GetTrainerFunc {
|
||||
return () => {
|
||||
const rand = Utils.randSeedInt(trainerPool.length);
|
||||
const trainerTypes: TrainerType[] = [];
|
||||
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
for (const trainerPoolEntry of trainerPool) {
|
||||
const trainerType = Array.isArray(trainerPoolEntry)
|
||||
? Utils.randSeedItem(trainerPoolEntry)
|
||||
: trainerPoolEntry;
|
||||
const trainerType = Array.isArray(trainerPoolEntry) ? Utils.randSeedItem(trainerPoolEntry) : trainerPoolEntry;
|
||||
trainerTypes.push(trainerType);
|
||||
}
|
||||
}, seedOffset);
|
||||
|
||||
let trainerGender = TrainerVariant.DEFAULT;
|
||||
if (randomGender) {
|
||||
trainerGender = (Utils.randInt(2) === 0) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT;
|
||||
trainerGender = Utils.randInt(2) === 0 ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT;
|
||||
}
|
||||
|
||||
/* 1/3 chance for evil team grunts to be double battles */
|
||||
const evilTeamGrunts = [ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ];
|
||||
const evilTeamGrunts = [
|
||||
TrainerType.ROCKET_GRUNT,
|
||||
TrainerType.MAGMA_GRUNT,
|
||||
TrainerType.AQUA_GRUNT,
|
||||
TrainerType.GALACTIC_GRUNT,
|
||||
TrainerType.PLASMA_GRUNT,
|
||||
TrainerType.FLARE_GRUNT,
|
||||
TrainerType.AETHER_GRUNT,
|
||||
TrainerType.SKULL_GRUNT,
|
||||
TrainerType.MACRO_GRUNT,
|
||||
TrainerType.STAR_GRUNT,
|
||||
];
|
||||
const isEvilTeamGrunt = evilTeamGrunts.includes(trainerTypes[rand]);
|
||||
|
||||
if (trainerConfigs[trainerTypes[rand]].hasDouble && isEvilTeamGrunt) {
|
||||
return new Trainer(trainerTypes[rand], (Utils.randInt(3) === 0) ? TrainerVariant.DOUBLE : trainerGender);
|
||||
return new Trainer(trainerTypes[rand], Utils.randInt(3) === 0 ? TrainerVariant.DOUBLE : trainerGender);
|
||||
}
|
||||
|
||||
return new Trainer(trainerTypes[rand], trainerGender);
|
||||
|
@ -556,7 +593,7 @@ export function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[
|
|||
}
|
||||
|
||||
export interface FixedBattleConfigs {
|
||||
[key: number]: FixedBattleConfig
|
||||
[key: number]: FixedBattleConfig;
|
||||
}
|
||||
/**
|
||||
* Youngster/Lass on 5
|
||||
|
@ -568,51 +605,355 @@ export interface FixedBattleConfigs {
|
|||
* Champion on 190
|
||||
*/
|
||||
export const classicFixedBattles: FixedBattleConfigs = {
|
||||
[ClassicFixedBossWaves.TOWN_YOUNGSTER]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(() => new Trainer(TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
|
||||
[ClassicFixedBossWaves.RIVAL_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
|
||||
[ClassicFixedBossWaves.RIVAL_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_2, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
|
||||
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)),
|
||||
[ClassicFixedBossWaves.RIVAL_3]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_3, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
|
||||
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_3]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)),
|
||||
[ClassicFixedBossWaves.EVIL_ADMIN_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.COLRESS ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA, [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]], true)),
|
||||
[ClassicFixedBossWaves.RIVAL_4]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_4, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
|
||||
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)),
|
||||
[ClassicFixedBossWaves.EVIL_ADMIN_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.COLRESS ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA, [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]], true, 1)),
|
||||
[ClassicFixedBossWaves.EVIL_BOSS_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.MAXIE, TrainerType.ARCHIE, TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE, TrainerType.LUSAMINE, TrainerType.GUZMA, TrainerType.ROSE, TrainerType.PENNY ]))
|
||||
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }),
|
||||
[ClassicFixedBossWaves.RIVAL_5]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_5, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
|
||||
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }),
|
||||
[ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_2, TrainerType.MAXIE_2, TrainerType.ARCHIE_2, TrainerType.CYRUS_2, TrainerType.GHETSIS_2, TrainerType.LYSANDRE_2, TrainerType.LUSAMINE_2, TrainerType.GUZMA_2, TrainerType.ROSE_2, TrainerType.PENNY_2 ]))
|
||||
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }),
|
||||
[ClassicFixedBossWaves.ELITE_FOUR_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, [ TrainerType.HALA, TrainerType.MOLAYNE ], TrainerType.MARNIE_ELITE, TrainerType.RIKA, TrainerType.CRISPIN ])),
|
||||
[ClassicFixedBossWaves.ELITE_FOUR_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY, TrainerType.AMARYS ])),
|
||||
[ClassicFixedBossWaves.ELITE_FOUR_3]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, [ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ], TrainerType.LARRY_ELITE, TrainerType.LACEY ])),
|
||||
[ClassicFixedBossWaves.ELITE_FOUR_4]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL, TrainerType.DRAYTON ])),
|
||||
[ClassicFixedBossWaves.CHAMPION]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BLUE, [ TrainerType.RED, TrainerType.LANCE_CHAMPION ], [ TrainerType.STEVEN, TrainerType.WALLACE ], TrainerType.CYNTHIA, [ TrainerType.ALDER, TrainerType.IRIS ], TrainerType.DIANTHA, [ TrainerType.KUKUI, TrainerType.HAU ], [ TrainerType.LEON, TrainerType.MUSTARD ], [ TrainerType.GEETA, TrainerType.NEMONA ], TrainerType.KIERAN ])),
|
||||
[ClassicFixedBossWaves.RIVAL_6]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_6, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT))
|
||||
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false })
|
||||
[ClassicFixedBossWaves.TOWN_YOUNGSTER]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(
|
||||
() => new Trainer(TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT),
|
||||
),
|
||||
[ClassicFixedBossWaves.RIVAL_1]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(
|
||||
() =>
|
||||
new Trainer(
|
||||
TrainerType.RIVAL,
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
),
|
||||
[ClassicFixedBossWaves.RIVAL_2]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(
|
||||
() =>
|
||||
new Trainer(
|
||||
TrainerType.RIVAL_2,
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc(
|
||||
[
|
||||
TrainerType.ROCKET_GRUNT,
|
||||
TrainerType.MAGMA_GRUNT,
|
||||
TrainerType.AQUA_GRUNT,
|
||||
TrainerType.GALACTIC_GRUNT,
|
||||
TrainerType.PLASMA_GRUNT,
|
||||
TrainerType.FLARE_GRUNT,
|
||||
TrainerType.AETHER_GRUNT,
|
||||
TrainerType.SKULL_GRUNT,
|
||||
TrainerType.MACRO_GRUNT,
|
||||
TrainerType.STAR_GRUNT,
|
||||
],
|
||||
true,
|
||||
),
|
||||
),
|
||||
[ClassicFixedBossWaves.RIVAL_3]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(
|
||||
() =>
|
||||
new Trainer(
|
||||
TrainerType.RIVAL_3,
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc(
|
||||
[
|
||||
TrainerType.ROCKET_GRUNT,
|
||||
TrainerType.MAGMA_GRUNT,
|
||||
TrainerType.AQUA_GRUNT,
|
||||
TrainerType.GALACTIC_GRUNT,
|
||||
TrainerType.PLASMA_GRUNT,
|
||||
TrainerType.FLARE_GRUNT,
|
||||
TrainerType.AETHER_GRUNT,
|
||||
TrainerType.SKULL_GRUNT,
|
||||
TrainerType.MACRO_GRUNT,
|
||||
TrainerType.STAR_GRUNT,
|
||||
],
|
||||
true,
|
||||
),
|
||||
),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_3]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc(
|
||||
[
|
||||
TrainerType.ROCKET_GRUNT,
|
||||
TrainerType.MAGMA_GRUNT,
|
||||
TrainerType.AQUA_GRUNT,
|
||||
TrainerType.GALACTIC_GRUNT,
|
||||
TrainerType.PLASMA_GRUNT,
|
||||
TrainerType.FLARE_GRUNT,
|
||||
TrainerType.AETHER_GRUNT,
|
||||
TrainerType.SKULL_GRUNT,
|
||||
TrainerType.MACRO_GRUNT,
|
||||
TrainerType.STAR_GRUNT,
|
||||
],
|
||||
true,
|
||||
),
|
||||
),
|
||||
[ClassicFixedBossWaves.EVIL_ADMIN_1]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc(
|
||||
[
|
||||
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
|
||||
[TrainerType.TABITHA, TrainerType.COURTNEY],
|
||||
[TrainerType.MATT, TrainerType.SHELLY],
|
||||
[TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN],
|
||||
[TrainerType.ZINZOLIN, TrainerType.COLRESS],
|
||||
[TrainerType.XEROSIC, TrainerType.BRYONY],
|
||||
TrainerType.FABA,
|
||||
TrainerType.PLUMERIA,
|
||||
TrainerType.OLEANA,
|
||||
[TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI],
|
||||
],
|
||||
true,
|
||||
),
|
||||
),
|
||||
[ClassicFixedBossWaves.RIVAL_4]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(
|
||||
() =>
|
||||
new Trainer(
|
||||
TrainerType.RIVAL_4,
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc(
|
||||
[
|
||||
TrainerType.ROCKET_GRUNT,
|
||||
TrainerType.MAGMA_GRUNT,
|
||||
TrainerType.AQUA_GRUNT,
|
||||
TrainerType.GALACTIC_GRUNT,
|
||||
TrainerType.PLASMA_GRUNT,
|
||||
TrainerType.FLARE_GRUNT,
|
||||
TrainerType.AETHER_GRUNT,
|
||||
TrainerType.SKULL_GRUNT,
|
||||
TrainerType.MACRO_GRUNT,
|
||||
TrainerType.STAR_GRUNT,
|
||||
],
|
||||
true,
|
||||
),
|
||||
),
|
||||
[ClassicFixedBossWaves.EVIL_ADMIN_2]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc(
|
||||
[
|
||||
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
|
||||
[TrainerType.TABITHA, TrainerType.COURTNEY],
|
||||
[TrainerType.MATT, TrainerType.SHELLY],
|
||||
[TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN],
|
||||
[TrainerType.ZINZOLIN, TrainerType.COLRESS],
|
||||
[TrainerType.XEROSIC, TrainerType.BRYONY],
|
||||
TrainerType.FABA,
|
||||
TrainerType.PLUMERIA,
|
||||
TrainerType.OLEANA,
|
||||
[TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI],
|
||||
],
|
||||
true,
|
||||
1,
|
||||
),
|
||||
),
|
||||
[ClassicFixedBossWaves.EVIL_BOSS_1]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc([
|
||||
TrainerType.ROCKET_BOSS_GIOVANNI_1,
|
||||
TrainerType.MAXIE,
|
||||
TrainerType.ARCHIE,
|
||||
TrainerType.CYRUS,
|
||||
TrainerType.GHETSIS,
|
||||
TrainerType.LYSANDRE,
|
||||
TrainerType.LUSAMINE,
|
||||
TrainerType.GUZMA,
|
||||
TrainerType.ROSE,
|
||||
TrainerType.PENNY,
|
||||
]),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.RIVAL_5]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(
|
||||
() =>
|
||||
new Trainer(
|
||||
TrainerType.RIVAL_5,
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc([
|
||||
TrainerType.ROCKET_BOSS_GIOVANNI_2,
|
||||
TrainerType.MAXIE_2,
|
||||
TrainerType.ARCHIE_2,
|
||||
TrainerType.CYRUS_2,
|
||||
TrainerType.GHETSIS_2,
|
||||
TrainerType.LYSANDRE_2,
|
||||
TrainerType.LUSAMINE_2,
|
||||
TrainerType.GUZMA_2,
|
||||
TrainerType.ROSE_2,
|
||||
TrainerType.PENNY_2,
|
||||
]),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
[ClassicFixedBossWaves.ELITE_FOUR_1]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc([
|
||||
TrainerType.LORELEI,
|
||||
TrainerType.WILL,
|
||||
TrainerType.SIDNEY,
|
||||
TrainerType.AARON,
|
||||
TrainerType.SHAUNTAL,
|
||||
TrainerType.MALVA,
|
||||
[TrainerType.HALA, TrainerType.MOLAYNE],
|
||||
TrainerType.MARNIE_ELITE,
|
||||
TrainerType.RIKA,
|
||||
TrainerType.CRISPIN,
|
||||
]),
|
||||
),
|
||||
[ClassicFixedBossWaves.ELITE_FOUR_2]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc([
|
||||
TrainerType.BRUNO,
|
||||
TrainerType.KOGA,
|
||||
TrainerType.PHOEBE,
|
||||
TrainerType.BERTHA,
|
||||
TrainerType.MARSHAL,
|
||||
TrainerType.SIEBOLD,
|
||||
TrainerType.OLIVIA,
|
||||
TrainerType.NESSA_ELITE,
|
||||
TrainerType.POPPY,
|
||||
TrainerType.AMARYS,
|
||||
]),
|
||||
),
|
||||
[ClassicFixedBossWaves.ELITE_FOUR_3]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc([
|
||||
TrainerType.AGATHA,
|
||||
TrainerType.BRUNO,
|
||||
TrainerType.GLACIA,
|
||||
TrainerType.FLINT,
|
||||
TrainerType.GRIMSLEY,
|
||||
TrainerType.WIKSTROM,
|
||||
TrainerType.ACEROLA,
|
||||
[TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE],
|
||||
TrainerType.LARRY_ELITE,
|
||||
TrainerType.LACEY,
|
||||
]),
|
||||
),
|
||||
[ClassicFixedBossWaves.ELITE_FOUR_4]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc([
|
||||
TrainerType.LANCE,
|
||||
TrainerType.KAREN,
|
||||
TrainerType.DRAKE,
|
||||
TrainerType.LUCIAN,
|
||||
TrainerType.CAITLIN,
|
||||
TrainerType.DRASNA,
|
||||
TrainerType.KAHILI,
|
||||
TrainerType.RAIHAN_ELITE,
|
||||
TrainerType.HASSEL,
|
||||
TrainerType.DRAYTON,
|
||||
]),
|
||||
),
|
||||
[ClassicFixedBossWaves.CHAMPION]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1)
|
||||
.setGetTrainerFunc(
|
||||
getRandomTrainerFunc([
|
||||
TrainerType.BLUE,
|
||||
[TrainerType.RED, TrainerType.LANCE_CHAMPION],
|
||||
[TrainerType.STEVEN, TrainerType.WALLACE],
|
||||
TrainerType.CYNTHIA,
|
||||
[TrainerType.ALDER, TrainerType.IRIS],
|
||||
TrainerType.DIANTHA,
|
||||
[TrainerType.KUKUI, TrainerType.HAU],
|
||||
[TrainerType.LEON, TrainerType.MUSTARD],
|
||||
[TrainerType.GEETA, TrainerType.NEMONA],
|
||||
TrainerType.KIERAN,
|
||||
]),
|
||||
),
|
||||
[ClassicFixedBossWaves.RIVAL_6]: new FixedBattleConfig()
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(
|
||||
() =>
|
||||
new Trainer(
|
||||
TrainerType.RIVAL_6,
|
||||
globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
),
|
||||
)
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.GREAT,
|
||||
ModifierTier.GREAT,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -77,7 +77,7 @@ const cfg_keyboard_qwerty = {
|
|||
KEY_RIGHT_BRACKET: Phaser.Input.Keyboard.KeyCodes.CLOSED_BRACKET,
|
||||
KEY_SEMICOLON: Phaser.Input.Keyboard.KeyCodes.SEMICOLON,
|
||||
KEY_BACKSPACE: Phaser.Input.Keyboard.KeyCodes.BACKSPACE,
|
||||
KEY_ALT: Phaser.Input.Keyboard.KeyCodes.ALT
|
||||
KEY_ALT: Phaser.Input.Keyboard.KeyCodes.ALT,
|
||||
},
|
||||
icons: {
|
||||
KEY_A: "A.png",
|
||||
|
@ -131,7 +131,6 @@ const cfg_keyboard_qwerty = {
|
|||
KEY_F11: "F11.png",
|
||||
KEY_F12: "F12.png",
|
||||
|
||||
|
||||
KEY_PAGE_DOWN: "PAGE_DOWN.png",
|
||||
KEY_PAGE_UP: "PAGE_UP.png",
|
||||
|
||||
|
@ -163,7 +162,7 @@ const cfg_keyboard_qwerty = {
|
|||
KEY_SEMICOLON: "SEMICOLON.png",
|
||||
|
||||
KEY_BACKSPACE: "BACK.png",
|
||||
KEY_ALT: "ALT.png"
|
||||
KEY_ALT: "ALT.png",
|
||||
},
|
||||
settings: {
|
||||
[SettingKeyboard.Button_Up]: Button.UP,
|
||||
|
@ -274,7 +273,7 @@ const cfg_keyboard_qwerty = {
|
|||
KEY_LEFT_BRACKET: -1,
|
||||
KEY_RIGHT_BRACKET: -1,
|
||||
KEY_SEMICOLON: -1,
|
||||
KEY_ALT: -1
|
||||
KEY_ALT: -1,
|
||||
},
|
||||
blacklist: [
|
||||
"KEY_ENTER",
|
||||
|
@ -287,7 +286,7 @@ const cfg_keyboard_qwerty = {
|
|||
"KEY_ARROW_RIGHT",
|
||||
"KEY_DEL",
|
||||
"KEY_HOME",
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
export default cfg_keyboard_qwerty;
|
||||
|
|
|
@ -93,7 +93,7 @@ export function getIconWithSettingName(config, settingName) {
|
|||
}
|
||||
|
||||
export function getIconForLatestInput(configs, source, devices, settingName) {
|
||||
let config;
|
||||
let config: any; // TODO: refine type
|
||||
if (source === "gamepad") {
|
||||
config = configs[devices[Device.GAMEPAD]];
|
||||
} else {
|
||||
|
@ -102,7 +102,7 @@ export function getIconForLatestInput(configs, source, devices, settingName) {
|
|||
const icon = getIconWithSettingName(config, settingName);
|
||||
if (!icon) {
|
||||
const isAlt = settingName.includes("ALT_");
|
||||
let altSettingName;
|
||||
let altSettingName: string;
|
||||
if (isAlt) {
|
||||
altSettingName = settingName.split("ALT_").splice(1)[0];
|
||||
} else {
|
||||
|
@ -115,7 +115,10 @@ export function getIconForLatestInput(configs, source, devices, settingName) {
|
|||
|
||||
export function assign(config, settingNameTarget, keycode): boolean {
|
||||
// first, we need to check if this keycode is already used on another settingName
|
||||
if (!canIAssignThisKey(config, getKeyWithKeycode(config, keycode)) || !canIOverrideThisSetting(config, settingNameTarget)) {
|
||||
if (
|
||||
!canIAssignThisKey(config, getKeyWithKeycode(config, keycode)) ||
|
||||
!canIOverrideThisSetting(config, settingNameTarget)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const previousSettingName = getSettingNameWithKeycode(config, keycode);
|
||||
|
|
|
@ -24,7 +24,7 @@ const pad_dualshock = {
|
|||
LC_S: 13,
|
||||
LC_W: 14,
|
||||
LC_E: 15,
|
||||
TOUCH: 17
|
||||
TOUCH: 17,
|
||||
},
|
||||
icons: {
|
||||
RC_S: "CROSS.png",
|
||||
|
@ -43,7 +43,7 @@ const pad_dualshock = {
|
|||
LC_S: "DOWN.png",
|
||||
LC_W: "LEFT.png",
|
||||
LC_E: "RIGHT.png",
|
||||
TOUCH: "TOUCH.png"
|
||||
TOUCH: "TOUCH.png",
|
||||
},
|
||||
settings: {
|
||||
[SettingGamepad.Button_Up]: Button.UP,
|
||||
|
@ -56,13 +56,13 @@ const pad_dualshock = {
|
|||
[SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
|
||||
[SettingGamepad.Button_Menu]: Button.MENU,
|
||||
[SettingGamepad.Button_Stats]: Button.STATS,
|
||||
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
|
||||
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
|
||||
[SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY,
|
||||
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
|
||||
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
|
||||
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
|
||||
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
|
||||
[SettingGamepad.Button_Submit]: Button.SUBMIT
|
||||
[SettingGamepad.Button_Submit]: Button.SUBMIT,
|
||||
},
|
||||
default: {
|
||||
LC_N: SettingGamepad.Button_Up,
|
||||
|
|
|
@ -23,7 +23,7 @@ const pad_generic = {
|
|||
LC_N: 12,
|
||||
LC_S: 13,
|
||||
LC_W: 14,
|
||||
LC_E: 15
|
||||
LC_E: 15,
|
||||
},
|
||||
icons: {
|
||||
RC_S: "XB_Letter_A_OL.png",
|
||||
|
@ -54,12 +54,12 @@ const pad_generic = {
|
|||
[SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
|
||||
[SettingGamepad.Button_Menu]: Button.MENU,
|
||||
[SettingGamepad.Button_Stats]: Button.STATS,
|
||||
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
|
||||
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
|
||||
[SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY,
|
||||
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
|
||||
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
|
||||
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
|
||||
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN
|
||||
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
|
||||
},
|
||||
default: {
|
||||
LC_N: SettingGamepad.Button_Up,
|
||||
|
@ -77,14 +77,9 @@ const pad_generic = {
|
|||
LT: SettingGamepad.Button_Cycle_Gender,
|
||||
RT: SettingGamepad.Button_Cycle_Ability,
|
||||
LS: SettingGamepad.Button_Speed_Up,
|
||||
RS: SettingGamepad.Button_Slow_Down
|
||||
RS: SettingGamepad.Button_Slow_Down,
|
||||
},
|
||||
blacklist: [
|
||||
"LC_N",
|
||||
"LC_S",
|
||||
"LC_W",
|
||||
"LC_E",
|
||||
]
|
||||
blacklist: ["LC_N", "LC_S", "LC_W", "LC_E"],
|
||||
};
|
||||
|
||||
export default pad_generic;
|
||||
|
|
|
@ -55,12 +55,12 @@ const pad_procon = {
|
|||
[SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
|
||||
[SettingGamepad.Button_Menu]: Button.MENU,
|
||||
[SettingGamepad.Button_Stats]: Button.STATS,
|
||||
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
|
||||
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
|
||||
[SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY,
|
||||
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
|
||||
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
|
||||
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
|
||||
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN
|
||||
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
|
||||
},
|
||||
default: {
|
||||
LC_N: SettingGamepad.Button_Up,
|
||||
|
@ -78,7 +78,7 @@ const pad_procon = {
|
|||
LT: SettingGamepad.Button_Cycle_Gender,
|
||||
RT: SettingGamepad.Button_Cycle_Ability,
|
||||
LS: SettingGamepad.Button_Speed_Up,
|
||||
RS: SettingGamepad.Button_Slow_Down
|
||||
RS: SettingGamepad.Button_Slow_Down,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Button } from "#enums/buttons";
|
|||
const pad_unlicensedSNES = {
|
||||
padID: "081f-e401",
|
||||
padType: "xbox",
|
||||
deviceMapping : {
|
||||
deviceMapping: {
|
||||
RC_S: 2,
|
||||
RC_E: 1,
|
||||
RC_W: 3,
|
||||
|
@ -19,7 +19,7 @@ const pad_unlicensedSNES = {
|
|||
LC_N: 12,
|
||||
LC_S: 13,
|
||||
LC_W: 14,
|
||||
LC_E: 15
|
||||
LC_E: 15,
|
||||
},
|
||||
icons: {
|
||||
RC_S: "XB_Letter_A_OL.png",
|
||||
|
@ -46,12 +46,12 @@ const pad_unlicensedSNES = {
|
|||
[SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
|
||||
[SettingGamepad.Button_Menu]: Button.MENU,
|
||||
[SettingGamepad.Button_Stats]: Button.STATS,
|
||||
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
|
||||
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
|
||||
[SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY,
|
||||
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
|
||||
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
|
||||
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
|
||||
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN
|
||||
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
|
||||
},
|
||||
default: {
|
||||
LC_N: SettingGamepad.Button_Up,
|
||||
|
@ -69,7 +69,7 @@ const pad_unlicensedSNES = {
|
|||
LT: -1,
|
||||
RT: -1,
|
||||
LS: -1,
|
||||
RS: -1
|
||||
RS: -1,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ const pad_xbox360 = {
|
|||
LC_N: 12,
|
||||
LC_S: 13,
|
||||
LC_W: 14,
|
||||
LC_E: 15
|
||||
LC_E: 15,
|
||||
},
|
||||
icons: {
|
||||
RC_S: "XB_Letter_A_OL.png",
|
||||
|
@ -54,12 +54,12 @@ const pad_xbox360 = {
|
|||
[SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
|
||||
[SettingGamepad.Button_Menu]: Button.MENU,
|
||||
[SettingGamepad.Button_Stats]: Button.STATS,
|
||||
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
|
||||
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
|
||||
[SettingGamepad.Button_Cycle_Shiny]: Button.CYCLE_SHINY,
|
||||
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
|
||||
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
|
||||
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
|
||||
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN
|
||||
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
|
||||
},
|
||||
default: {
|
||||
LC_N: SettingGamepad.Button_Up,
|
||||
|
@ -77,7 +77,7 @@ const pad_xbox360 = {
|
|||
LT: SettingGamepad.Button_Cycle_Gender,
|
||||
RT: SettingGamepad.Button_Cycle_Ability,
|
||||
LS: SettingGamepad.Button_Speed_Up,
|
||||
RS: SettingGamepad.Button_Slow_Down
|
||||
RS: SettingGamepad.Button_Slow_Down,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -159,7 +159,7 @@ export abstract class AbAttr {
|
|||
public showAbility: boolean;
|
||||
private extraCondition: AbAttrCondition;
|
||||
|
||||
constructor(showAbility: boolean = true) {
|
||||
constructor(showAbility = true) {
|
||||
this.showAbility = showAbility;
|
||||
}
|
||||
|
||||
|
@ -775,7 +775,7 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr {
|
|||
private selfTarget: 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);
|
||||
|
||||
this.condition = condition;
|
||||
|
@ -813,7 +813,7 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
|
|||
private stages: number;
|
||||
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);
|
||||
|
||||
this.condition = condition;
|
||||
|
@ -1320,7 +1320,7 @@ export class FieldMultiplyStatAbAttr extends AbAttr {
|
|||
private multiplier: number;
|
||||
private canStack: boolean;
|
||||
|
||||
constructor(stat: Stat, multiplier: number, canStack: boolean = false) {
|
||||
constructor(stat: Stat, multiplier: number, canStack = false) {
|
||||
super(false);
|
||||
|
||||
this.stat = stat;
|
||||
|
@ -1510,7 +1510,7 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr {
|
|||
private condition: PokemonAttackCondition;
|
||||
private powerMultiplier: number;
|
||||
|
||||
constructor(condition: PokemonAttackCondition, powerMultiplier: number, showAbility: boolean = true) {
|
||||
constructor(condition: PokemonAttackCondition, powerMultiplier: number, showAbility = true) {
|
||||
super(showAbility);
|
||||
this.condition = condition;
|
||||
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 {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);
|
||||
this.mult = mult;
|
||||
}
|
||||
|
@ -1678,7 +1678,7 @@ export class PostAttackAbAttr extends AbAttr {
|
|||
private attackCondition: PokemonAttackCondition;
|
||||
|
||||
/** 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);
|
||||
|
||||
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 */
|
||||
private activateOnGain: boolean;
|
||||
|
||||
constructor(showAbility: boolean = true, activateOnGain: boolean = true) {
|
||||
constructor(showAbility = true, activateOnGain = true) {
|
||||
super(showAbility);
|
||||
this.activateOnGain = activateOnGain;
|
||||
}
|
||||
|
@ -2334,7 +2334,7 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr {
|
|||
private healRatio: number;
|
||||
private showAnim: boolean;
|
||||
|
||||
constructor(healRatio: number, showAnim: boolean = false) {
|
||||
constructor(healRatio: number, showAnim = false) {
|
||||
super();
|
||||
|
||||
this.healRatio = healRatio || 4;
|
||||
|
@ -3296,7 +3296,7 @@ export class MultCritAbAttr extends AbAttr {
|
|||
export class ConditionalCritAbAttr extends AbAttr {
|
||||
private condition: PokemonAttackCondition;
|
||||
|
||||
constructor(condition: PokemonAttackCondition, checkUser?: Boolean) {
|
||||
constructor(condition: PokemonAttackCondition, checkUser?: boolean) {
|
||||
super();
|
||||
|
||||
this.condition = condition;
|
||||
|
@ -3400,7 +3400,7 @@ export class IgnoreContactAbAttr extends AbAttr { }
|
|||
export class PreWeatherEffectAbAttr extends AbAttr {
|
||||
applyPreWeatherEffect(
|
||||
pokemon: Pokemon,
|
||||
passive: Boolean,
|
||||
passive: boolean,
|
||||
simulated: boolean,
|
||||
weather: Weather | null,
|
||||
cancelled: Utils.BooleanHolder,
|
||||
|
@ -3837,7 +3837,7 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr {
|
|||
private allyTarget: boolean;
|
||||
private target: Pokemon;
|
||||
|
||||
constructor(allyTarget: boolean = false) {
|
||||
constructor(allyTarget = false) {
|
||||
super(true);
|
||||
this.allyTarget = allyTarget;
|
||||
}
|
||||
|
@ -4049,7 +4049,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {
|
|||
* @returns `true` if any opponents are sleeping
|
||||
*/
|
||||
override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||
let hadEffect: boolean = false;
|
||||
let hadEffect = false;
|
||||
for (const opp of pokemon.getOpponents()) {
|
||||
if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) {
|
||||
if (!simulated) {
|
||||
|
@ -5150,9 +5150,9 @@ function applySingleAbAttrs<TAttr extends AbAttr>(
|
|||
attrType: Constructor<TAttr>,
|
||||
applyFunc: AbAttrApplyFunc<TAttr>,
|
||||
args: any[],
|
||||
gainedMidTurn: boolean = false,
|
||||
simulated: boolean = false,
|
||||
showAbilityInstant: boolean = false,
|
||||
gainedMidTurn = false,
|
||||
simulated = false,
|
||||
showAbilityInstant = false,
|
||||
messages: string[] = []
|
||||
) {
|
||||
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 hpRatio: number;
|
||||
|
||||
constructor(hpRatio: number = 0.5) {
|
||||
constructor(hpRatio = 0.5) {
|
||||
super();
|
||||
this.hpRatio = hpRatio;
|
||||
}
|
||||
|
@ -5446,10 +5446,10 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(
|
|||
pokemon: Pokemon | null,
|
||||
applyFunc: AbAttrApplyFunc<TAttr>,
|
||||
args: any[],
|
||||
showAbilityInstant: boolean = false,
|
||||
simulated: boolean = false,
|
||||
showAbilityInstant = false,
|
||||
simulated = false,
|
||||
messages: string[] = [],
|
||||
gainedMidTurn: boolean = false
|
||||
gainedMidTurn = false
|
||||
) {
|
||||
for (const passive of [ false, true ]) {
|
||||
if (pokemon) {
|
||||
|
@ -5463,7 +5463,7 @@ export function applyAbAttrs(
|
|||
attrType: Constructor<AbAttr>,
|
||||
pokemon: Pokemon,
|
||||
cancelled: Utils.BooleanHolder | null,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<AbAttr>(
|
||||
|
@ -5479,7 +5479,7 @@ export function applyAbAttrs(
|
|||
export function applyPostBattleInitAbAttrs(
|
||||
attrType: Constructor<PostBattleInitAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostBattleInitAbAttr>(
|
||||
|
@ -5498,7 +5498,7 @@ export function applyPreDefendAbAttrs(
|
|||
attacker: Pokemon,
|
||||
move: Move | null,
|
||||
cancelled: Utils.BooleanHolder | null,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PreDefendAbAttr>(
|
||||
|
@ -5517,7 +5517,7 @@ export function applyPostDefendAbAttrs(
|
|||
attacker: Pokemon,
|
||||
move: Move,
|
||||
hitResult: HitResult | null,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostDefendAbAttr>(
|
||||
|
@ -5536,7 +5536,7 @@ export function applyPostMoveUsedAbAttrs(
|
|||
move: PokemonMove,
|
||||
source: Pokemon,
|
||||
targets: BattlerIndex[],
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostMoveUsedAbAttr>(
|
||||
|
@ -5554,7 +5554,7 @@ export function applyStatMultiplierAbAttrs(
|
|||
pokemon: Pokemon,
|
||||
stat: BattleStat,
|
||||
statValue: Utils.NumberHolder,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<StatMultiplierAbAttr>(
|
||||
|
@ -5569,7 +5569,7 @@ export function applyPostSetStatusAbAttrs(
|
|||
pokemon: Pokemon,
|
||||
effect: StatusEffect,
|
||||
sourcePokemon?: Pokemon | null,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostSetStatusAbAttr>(
|
||||
|
@ -5587,7 +5587,7 @@ export function applyPostDamageAbAttrs(
|
|||
pokemon: Pokemon,
|
||||
damage: number,
|
||||
passive: boolean,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
args: any[],
|
||||
source?: Pokemon,
|
||||
): void {
|
||||
|
@ -5616,7 +5616,7 @@ export function applyFieldStatMultiplierAbAttrs(
|
|||
statValue: Utils.NumberHolder,
|
||||
checkedPokemon: Pokemon,
|
||||
hasApplied: Utils.BooleanHolder,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<FieldMultiplyStatAbAttr>(
|
||||
|
@ -5633,7 +5633,7 @@ export function applyPreAttackAbAttrs(
|
|||
pokemon: Pokemon,
|
||||
defender: Pokemon | null,
|
||||
move: Move,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PreAttackAbAttr>(
|
||||
|
@ -5652,7 +5652,7 @@ export function applyPostAttackAbAttrs(
|
|||
defender: Pokemon,
|
||||
move: Move,
|
||||
hitResult: HitResult | null,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostAttackAbAttr>(
|
||||
|
@ -5669,7 +5669,7 @@ export function applyPostKnockOutAbAttrs(
|
|||
attrType: Constructor<PostKnockOutAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
knockedOut: Pokemon,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostKnockOutAbAttr>(
|
||||
|
@ -5685,7 +5685,7 @@ export function applyPostKnockOutAbAttrs(
|
|||
export function applyPostVictoryAbAttrs(
|
||||
attrType: Constructor<PostVictoryAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostVictoryAbAttr>(
|
||||
|
@ -5701,7 +5701,7 @@ export function applyPostVictoryAbAttrs(
|
|||
export function applyPostSummonAbAttrs(
|
||||
attrType: Constructor<PostSummonAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostSummonAbAttr>(
|
||||
|
@ -5717,7 +5717,7 @@ export function applyPostSummonAbAttrs(
|
|||
export function applyPreSwitchOutAbAttrs(
|
||||
attrType: Constructor<PreSwitchOutAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PreSwitchOutAbAttr>(
|
||||
|
@ -5733,7 +5733,7 @@ export function applyPreSwitchOutAbAttrs(
|
|||
export function applyPreLeaveFieldAbAttrs(
|
||||
attrType: Constructor<PreLeaveFieldAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
return applyAbAttrsInternal<PreLeaveFieldAbAttr>(
|
||||
|
@ -5752,7 +5752,7 @@ export function applyPreStatStageChangeAbAttrs(
|
|||
pokemon: Pokemon | null,
|
||||
stat: BattleStat,
|
||||
cancelled: Utils.BooleanHolder,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PreStatStageChangeAbAttr>(
|
||||
|
@ -5771,7 +5771,7 @@ export function applyPostStatStageChangeAbAttrs(
|
|||
stats: BattleStat[],
|
||||
stages: integer,
|
||||
selfTarget: boolean,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostStatStageChangeAbAttr>(
|
||||
|
@ -5789,7 +5789,7 @@ export function applyPreSetStatusAbAttrs(
|
|||
pokemon: Pokemon,
|
||||
effect: StatusEffect | undefined,
|
||||
cancelled: Utils.BooleanHolder,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PreSetStatusAbAttr>(
|
||||
|
@ -5807,7 +5807,7 @@ export function applyPreApplyBattlerTagAbAttrs(
|
|||
pokemon: Pokemon,
|
||||
tag: BattlerTag,
|
||||
cancelled: Utils.BooleanHolder,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PreApplyBattlerTagAbAttr>(
|
||||
|
@ -5825,7 +5825,7 @@ export function applyPreWeatherEffectAbAttrs(
|
|||
pokemon: Pokemon,
|
||||
weather: Weather | null,
|
||||
cancelled: Utils.BooleanHolder,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PreWeatherDamageAbAttr>(
|
||||
|
@ -5841,7 +5841,7 @@ export function applyPreWeatherEffectAbAttrs(
|
|||
export function applyPostTurnAbAttrs(
|
||||
attrType: Constructor<PostTurnAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostTurnAbAttr>(
|
||||
|
@ -5858,7 +5858,7 @@ export function applyPostWeatherChangeAbAttrs(
|
|||
attrType: Constructor<PostWeatherChangeAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
weather: WeatherType,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostWeatherChangeAbAttr>(
|
||||
|
@ -5875,7 +5875,7 @@ export function applyPostWeatherLapseAbAttrs(
|
|||
attrType: Constructor<PostWeatherLapseAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
weather: Weather | null,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostWeatherLapseAbAttr>(
|
||||
|
@ -5892,7 +5892,7 @@ export function applyPostTerrainChangeAbAttrs(
|
|||
attrType: Constructor<PostTerrainChangeAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
terrain: TerrainType,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostTerrainChangeAbAttr>(
|
||||
|
@ -5911,7 +5911,7 @@ export function applyCheckTrappedAbAttrs(
|
|||
trapped: Utils.BooleanHolder,
|
||||
otherPokemon: Pokemon,
|
||||
messages: string[],
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<CheckTrappedAbAttr>(
|
||||
|
@ -5928,7 +5928,7 @@ export function applyCheckTrappedAbAttrs(
|
|||
export function applyPostBattleAbAttrs(
|
||||
attrType: Constructor<PostBattleAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostBattleAbAttr>(
|
||||
|
@ -5947,7 +5947,7 @@ export function applyPostFaintAbAttrs(
|
|||
attacker?: Pokemon,
|
||||
move?: Move,
|
||||
hitResult?: HitResult,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PostFaintAbAttr>(
|
||||
|
@ -5963,7 +5963,7 @@ export function applyPostFaintAbAttrs(
|
|||
export function applyPostItemLostAbAttrs(
|
||||
attrType: Constructor<PostItemLostAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
simulated: boolean = false,
|
||||
simulated = false,
|
||||
...args: any[]
|
||||
): void {
|
||||
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
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
function queueShowAbility(pokemon: Pokemon, passive: boolean): void {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2022,6 +2022,7 @@ export const biomeTrainerPools: BiomeTrainerPools = {
|
|||
}
|
||||
};
|
||||
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: init methods are expected to have many lines.
|
||||
export function initBiomes() {
|
||||
const pokemonBiomes = [
|
||||
[ Species.BULBASAUR, PokemonType.GRASS, PokemonType.POISON, [
|
||||
|
@ -7677,7 +7678,7 @@ export function initBiomes() {
|
|||
|
||||
const traverseBiome = (biome: Biome, depth: number) => {
|
||||
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
|
||||
const randIndex = Utils.randSeedInt(biomeList.length, 1); // Will never be Biome.TOWN
|
||||
biome = Biome[biomeList[randIndex]];
|
||||
|
@ -7764,7 +7765,8 @@ export function initBiomes() {
|
|||
treeIndex = t;
|
||||
arrayIndex = es + 1;
|
||||
break;
|
||||
} else if (speciesEvolutions && speciesEvolutions.find(se => se.speciesId === existingSpeciesId)) {
|
||||
}
|
||||
if (speciesEvolutions?.find(se => se.speciesId === existingSpeciesId)) {
|
||||
treeIndex = t;
|
||||
arrayIndex = es;
|
||||
break;
|
||||
|
@ -7786,7 +7788,7 @@ export function initBiomes() {
|
|||
|
||||
for (const b of Object.keys(biomePokemonPools)) {
|
||||
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])) {
|
||||
const biomeTierTimePool = biomePokemonPools[b][t][tod];
|
||||
for (let e = 0; e < biomeTierTimePool.length; e++) {
|
||||
|
@ -7799,7 +7801,7 @@ export function initBiomes() {
|
|||
};
|
||||
for (let s = 1; s < entry.length; 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);
|
||||
if (!newEntry.hasOwnProperty(level)) {
|
||||
newEntry[level] = [ speciesId ];
|
||||
|
|
|
@ -591,7 +591,7 @@ function parseEggMoves(content: string): void {
|
|||
const speciesValues = Utils.getEnumValues(Species);
|
||||
const lines = content.split(/\n/g);
|
||||
|
||||
lines.forEach((line, l) => {
|
||||
for (const line of lines) {
|
||||
const cols = line.split(",").slice(0, 5);
|
||||
const moveNames = allMoves.map(m => m.name.replace(/ \([A-Z]\)$/, "").toLowerCase());
|
||||
const enumSpeciesName = cols[0].toUpperCase().replace(/[ -]/g, "_");
|
||||
|
@ -612,7 +612,7 @@ function parseEggMoves(content: string): void {
|
|||
if (eggMoves.find(m => m !== Moves.NONE)) {
|
||||
output += `[Species.${Species[species]}]: [ ${eggMoves.map(m => `Moves.${Moves[m]}`).join(", ")} ],\n`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log(output);
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ export class SpeciesFormEvolution {
|
|||
public item: EvolutionItem | null;
|
||||
public condition: SpeciesEvolutionCondition | null;
|
||||
public wildDelay: SpeciesWildEvolutionDelay;
|
||||
public description: string = "";
|
||||
public description = "";
|
||||
|
||||
constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) {
|
||||
this.speciesId = speciesId;
|
||||
|
@ -206,7 +206,7 @@ class FriendshipTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition {
|
|||
super(p => p.friendship >= amount && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT));
|
||||
this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ];
|
||||
} else {
|
||||
super(p => false);
|
||||
super(_p => false);
|
||||
this.timesOfDay = [];
|
||||
}
|
||||
this.amount = amount;
|
||||
|
@ -1898,7 +1898,7 @@ export function initPokemonPrevolutions(): void {
|
|||
if (ev.evoFormKey && megaFormKeys.indexOf(ev.evoFormKey) > -1) {
|
||||
continue;
|
||||
}
|
||||
pokemonPrevolutions[ev.speciesId] = parseInt(pk) as Species;
|
||||
pokemonPrevolutions[ev.speciesId] = Number.parseInt(pk) as Species;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -3,7 +3,13 @@ import type Pokemon from "../field/pokemon";
|
|||
import { HitResult } from "../field/pokemon";
|
||||
import { getStatusEffectHealText } from "./status-effect";
|
||||
import * as Utils from "../utils";
|
||||
import { DoubleBerryEffectAbAttr, PostItemLostAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs, applyPostItemLostAbAttrs } from "./ability";
|
||||
import {
|
||||
DoubleBerryEffectAbAttr,
|
||||
PostItemLostAbAttr,
|
||||
ReduceBerryUseThresholdAbAttr,
|
||||
applyAbAttrs,
|
||||
applyPostItemLostAbAttrs,
|
||||
} from "./ability";
|
||||
import i18next from "i18next";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
|
@ -29,7 +35,8 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
|
|||
case BerryType.LUM:
|
||||
return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED);
|
||||
case BerryType.ENIGMA:
|
||||
return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length;
|
||||
return (pokemon: Pokemon) =>
|
||||
!!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length;
|
||||
case BerryType.LIECHI:
|
||||
case BerryType.GANLON:
|
||||
case BerryType.PETAYA:
|
||||
|
@ -75,8 +82,17 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||
}
|
||||
const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4));
|
||||
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed);
|
||||
globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(),
|
||||
hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true));
|
||||
globalScene.unshiftPhase(
|
||||
new PokemonHealPhase(
|
||||
pokemon.getBattlerIndex(),
|
||||
hpHealed.value,
|
||||
i18next.t("battle:hpHealBerry", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
berryName: getBerryName(berryType),
|
||||
}),
|
||||
true,
|
||||
),
|
||||
);
|
||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false);
|
||||
};
|
||||
case BerryType.LUM:
|
||||
|
@ -104,7 +120,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
||||
const statStages = new Utils.NumberHolder(1);
|
||||
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages);
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [stat], statStages.value));
|
||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false);
|
||||
};
|
||||
case BerryType.LANSAT:
|
||||
|
@ -123,7 +139,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||
const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK);
|
||||
const stages = new Utils.NumberHolder(2);
|
||||
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages);
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ randStat ], stages.value));
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randStat], stages.value));
|
||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false);
|
||||
};
|
||||
case BerryType.LEPPA:
|
||||
|
@ -131,11 +147,19 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||
if (pokemon.battleData) {
|
||||
pokemon.battleData.berriesEaten.push(berryType);
|
||||
}
|
||||
const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct?
|
||||
const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio())
|
||||
? pokemon.getMoveset().find(m => !m?.getPpRatio())
|
||||
: pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct?
|
||||
if (ppRestoreMove !== undefined) {
|
||||
ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0);
|
||||
globalScene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) }));
|
||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false);
|
||||
ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0);
|
||||
globalScene.queueMessage(
|
||||
i18next.t("battle:ppHealBerry", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
moveName: ppRestoreMove!.getName(),
|
||||
berryName: getBerryName(berryType),
|
||||
}),
|
||||
);
|
||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -33,37 +33,37 @@ export enum ChallengeType {
|
|||
/**
|
||||
* Challenges which modify what starters you can choose
|
||||
* @see {@link Challenge.applyStarterChoice}
|
||||
*/
|
||||
*/
|
||||
STARTER_CHOICE,
|
||||
/**
|
||||
* Challenges which modify how many starter points you have
|
||||
* @see {@link Challenge.applyStarterPoints}
|
||||
*/
|
||||
*/
|
||||
STARTER_POINTS,
|
||||
/**
|
||||
* Challenges which modify how many starter points you have
|
||||
* @see {@link Challenge.applyStarterPointCost}
|
||||
*/
|
||||
*/
|
||||
STARTER_COST,
|
||||
/**
|
||||
* Challenges which modify your starters in some way
|
||||
* @see {@link Challenge.applyStarterModify}
|
||||
*/
|
||||
*/
|
||||
STARTER_MODIFY,
|
||||
/**
|
||||
* Challenges which limit which pokemon you can have in battle.
|
||||
* @see {@link Challenge.applyPokemonInBattle}
|
||||
*/
|
||||
*/
|
||||
POKEMON_IN_BATTLE,
|
||||
/**
|
||||
* Adds or modifies the fixed battles in a run
|
||||
* @see {@link Challenge.applyFixedBattle}
|
||||
*/
|
||||
*/
|
||||
FIXED_BATTLES,
|
||||
/**
|
||||
* Modifies the effectiveness of Type matchups in battle
|
||||
* @see {@linkcode Challenge.applyTypeEffectiveness}
|
||||
*/
|
||||
*/
|
||||
TYPE_EFFECTIVENESS,
|
||||
/**
|
||||
* Modifies what level the AI pokemon are. UNIMPLEMENTED.
|
||||
|
@ -93,7 +93,6 @@ export enum ChallengeType {
|
|||
* Modifies what the pokemon stats for Flip Stat Mode.
|
||||
*/
|
||||
FLIP_STAT,
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,7 +105,7 @@ export enum MoveSourceType {
|
|||
GREAT_TM,
|
||||
ULTRA_TM,
|
||||
COMMON_EGG,
|
||||
RARE_EGG
|
||||
RARE_EGG,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,7 +147,10 @@ export abstract class Challenge {
|
|||
* @returns {@link string} The i18n key for this challenge
|
||||
*/
|
||||
geti18nKey(): string {
|
||||
return Challenges[this.id].split("_").map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("");
|
||||
return Challenges[this.id]
|
||||
.split("_")
|
||||
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
|
||||
.join("");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -195,7 +197,7 @@ export abstract class Challenge {
|
|||
*/
|
||||
getDescription(overrideValue?: number): string {
|
||||
const value = overrideValue ?? this.value;
|
||||
return `${i18next.t([ `challenges:${this.geti18nKey()}.desc.${value}`, `challenges:${this.geti18nKey()}.desc` ])}`;
|
||||
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${value}`, `challenges:${this.geti18nKey()}.desc`])}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -274,88 +276,93 @@ export abstract class Challenge {
|
|||
* @param source The source challenge or json.
|
||||
* @returns This challenge.
|
||||
*/
|
||||
static loadChallenge(source: Challenge | any): Challenge {
|
||||
static loadChallenge(_source: Challenge | any): Challenge {
|
||||
throw new Error("Method not implemented! Use derived class");
|
||||
}
|
||||
|
||||
/**
|
||||
* An apply function for STARTER_CHOICE challenges. Derived classes should alter this.
|
||||
* @param pokemon {@link PokemonSpecies} The pokemon to check the validity of.
|
||||
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
|
||||
* @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon.
|
||||
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
|
||||
* @param _pokemon {@link PokemonSpecies} The pokemon to check the validity of.
|
||||
* @param _valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
|
||||
* @param _dexAttr {@link DexAttrProps} The dex attributes of the pokemon.
|
||||
* @param _soft {@link boolean} If true, allow it if it could become a valid pokemon.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
|
||||
applyStarterChoice(
|
||||
_pokemon: PokemonSpecies,
|
||||
_valid: Utils.BooleanHolder,
|
||||
_dexAttr: DexAttrProps,
|
||||
_soft = false,
|
||||
): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An apply function for STARTER_POINTS challenges. Derived classes should alter this.
|
||||
* @param points {@link Utils.NumberHolder} The amount of points you have available.
|
||||
* @param _points {@link Utils.NumberHolder} The amount of points you have available.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyStarterPoints(points: Utils.NumberHolder): boolean {
|
||||
applyStarterPoints(_points: Utils.NumberHolder): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An apply function for STARTER_COST challenges. Derived classes should alter this.
|
||||
* @param species {@link Species} The pokemon to change the cost of.
|
||||
* @param cost {@link Utils.NumberHolder} The cost of the starter.
|
||||
* @param _species {@link Species} The pokemon to change the cost of.
|
||||
* @param _cost {@link Utils.NumberHolder} The cost of the starter.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean {
|
||||
applyStarterCost(_species: Species, _cost: Utils.NumberHolder): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An apply function for STARTER_MODIFY challenges. Derived classes should alter this.
|
||||
* @param pokemon {@link Pokemon} The starter pokemon to modify.
|
||||
* @param _pokemon {@link Pokemon} The starter pokemon to modify.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyStarterModify(pokemon: Pokemon): boolean {
|
||||
applyStarterModify(_pokemon: Pokemon): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An apply function for POKEMON_IN_BATTLE challenges. Derived classes should alter this.
|
||||
* @param pokemon {@link Pokemon} The pokemon to check the validity of.
|
||||
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
|
||||
* @param _pokemon {@link Pokemon} The pokemon to check the validity of.
|
||||
* @param _valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
|
||||
applyPokemonInBattle(_pokemon: Pokemon, _valid: Utils.BooleanHolder): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An apply function for FIXED_BATTLE challenges. Derived classes should alter this.
|
||||
* @param waveIndex {@link Number} The current wave index.
|
||||
* @param battleConfig {@link FixedBattleConfig} The battle config to modify.
|
||||
* @param _waveIndex {@link Number} The current wave index.
|
||||
* @param _battleConfig {@link FixedBattleConfig} The battle config to modify.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean {
|
||||
applyFixedBattle(_waveIndex: number, _battleConfig: FixedBattleConfig): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An apply function for TYPE_EFFECTIVENESS challenges. Derived classes should alter this.
|
||||
* @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move.
|
||||
* @param _effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move.
|
||||
* @returns Whether this function did anything.
|
||||
*/
|
||||
applyTypeEffectiveness(effectiveness: Utils.NumberHolder): boolean {
|
||||
applyTypeEffectiveness(_effectiveness: Utils.NumberHolder): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An apply function for AI_LEVEL challenges. Derived classes should alter this.
|
||||
* @param level {@link Utils.NumberHolder} The generated level.
|
||||
* @param levelCap {@link Number} The current level cap.
|
||||
* @param isTrainer {@link Boolean} Whether this is a trainer pokemon.
|
||||
* @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon.
|
||||
* @param _level {@link Utils.NumberHolder} The generated level.
|
||||
* @param _levelCap {@link Number} The current level cap.
|
||||
* @param _isTrainer {@link Boolean} Whether this is a trainer pokemon.
|
||||
* @param _isBoss {@link Boolean} Whether this is a non-trainer boss pokemon.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyLevelChange(level: Utils.NumberHolder, levelCap: number, isTrainer: boolean, isBoss: boolean): boolean {
|
||||
applyLevelChange(_level: Utils.NumberHolder, _levelCap: number, _isTrainer: boolean, _isBoss: boolean): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -365,7 +372,7 @@ export abstract class Challenge {
|
|||
* @param moveSlots {@link Utils.NumberHolder} The amount of move slots.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyMoveSlot(pokemon: Pokemon, moveSlots: Utils.NumberHolder): boolean {
|
||||
applyMoveSlot(_pokemon: Pokemon, _moveSlots: Utils.NumberHolder): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -375,7 +382,7 @@ export abstract class Challenge {
|
|||
* @param hasPassive {@link Utils.BooleanHolder} Whether it should have its passive.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyPassiveAccess(pokemon: Pokemon, hasPassive: Utils.BooleanHolder): boolean {
|
||||
applyPassiveAccess(_pokemon: Pokemon, _hasPassive: Utils.BooleanHolder): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -384,41 +391,46 @@ export abstract class Challenge {
|
|||
* @param gameMode {@link GameMode} The current game mode.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyGameModeModify(gameMode: GameMode): boolean {
|
||||
applyGameModeModify(_gameMode: GameMode): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An apply function for MOVE_ACCESS. Derived classes should alter this.
|
||||
* @param pokemon {@link Pokemon} What pokemon would learn the move.
|
||||
* @param moveSource {@link MoveSourceType} What source the pokemon would get the move from.
|
||||
* @param move {@link Moves} The move in question.
|
||||
* @param level {@link Utils.NumberHolder} The level threshold for access.
|
||||
* @param _pokemon {@link Pokemon} What pokemon would learn the move.
|
||||
* @param _moveSource {@link MoveSourceType} What source the pokemon would get the move from.
|
||||
* @param _move {@link Moves} The move in question.
|
||||
* @param _level {@link Utils.NumberHolder} The level threshold for access.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyMoveAccessLevel(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.NumberHolder): boolean {
|
||||
applyMoveAccessLevel(
|
||||
_pokemon: Pokemon,
|
||||
_moveSource: MoveSourceType,
|
||||
_move: Moves,
|
||||
_level: Utils.NumberHolder,
|
||||
): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An apply function for MOVE_WEIGHT. Derived classes should alter this.
|
||||
* @param pokemon {@link Pokemon} What pokemon would learn the move.
|
||||
* @param moveSource {@link MoveSourceType} What source the pokemon would get the move from.
|
||||
* @param move {@link Moves} The move in question.
|
||||
* @param weight {@link Utils.NumberHolder} The base weight of the move
|
||||
* @param _pokemon {@link Pokemon} What pokemon would learn the move.
|
||||
* @param _moveSource {@link MoveSourceType} What source the pokemon would get the move from.
|
||||
* @param _move {@link Moves} The move in question.
|
||||
* @param _weight {@link Utils.NumberHolder} The base weight of the move
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyMoveWeight(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.NumberHolder): boolean {
|
||||
applyMoveWeight(_pokemon: Pokemon, _moveSource: MoveSourceType, _move: Moves, _level: Utils.NumberHolder): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An apply function for FlipStats. Derived classes should alter this.
|
||||
* @param pokemon {@link Pokemon} What pokemon would learn the move.
|
||||
* @param baseStats What are the stats to flip.
|
||||
* @param _pokemon {@link Pokemon} What pokemon would learn the move.
|
||||
* @param _baseStats What are the stats to flip.
|
||||
* @returns {@link boolean} Whether this function did anything.
|
||||
*/
|
||||
applyFlipStat(pokemon: Pokemon, baseStats: number[]) {
|
||||
applyFlipStat(_pokemon: Pokemon, _baseStats: number[]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -433,10 +445,15 @@ export class SingleGenerationChallenge extends Challenge {
|
|||
super(Challenges.SINGLE_GENERATION, 9);
|
||||
}
|
||||
|
||||
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
|
||||
const generations = [ pokemon.generation ];
|
||||
applyStarterChoice(
|
||||
pokemon: PokemonSpecies,
|
||||
valid: Utils.BooleanHolder,
|
||||
_dexAttr: DexAttrProps,
|
||||
soft = false,
|
||||
): boolean {
|
||||
const generations = [pokemon.generation];
|
||||
if (soft) {
|
||||
const speciesToCheck = [ pokemon.speciesId ];
|
||||
const speciesToCheck = [pokemon.speciesId];
|
||||
while (speciesToCheck.length) {
|
||||
const checking = speciesToCheck.pop();
|
||||
if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
|
||||
|
@ -458,7 +475,10 @@ export class SingleGenerationChallenge extends Challenge {
|
|||
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
|
||||
const baseGeneration = getPokemonSpecies(pokemon.species.speciesId).generation;
|
||||
const fusionGeneration = pokemon.isFusion() ? getPokemonSpecies(pokemon.fusionSpecies!.speciesId).generation : 0; // TODO: is the bang on fusionSpecies correct?
|
||||
if (pokemon.isPlayer() && (baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))) {
|
||||
if (
|
||||
pokemon.isPlayer() &&
|
||||
(baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))
|
||||
) {
|
||||
valid.value = false;
|
||||
return true;
|
||||
}
|
||||
|
@ -467,11 +487,63 @@ export class SingleGenerationChallenge extends Challenge {
|
|||
|
||||
applyFixedBattle(waveIndex: number, battleConfig: FixedBattleConfig): boolean {
|
||||
let trainerTypes: (TrainerType | TrainerType[])[] = [];
|
||||
const evilTeamWaves: number[] = [ ClassicFixedBossWaves.EVIL_GRUNT_1, ClassicFixedBossWaves.EVIL_GRUNT_2, ClassicFixedBossWaves.EVIL_GRUNT_3, ClassicFixedBossWaves.EVIL_ADMIN_1, ClassicFixedBossWaves.EVIL_GRUNT_4, ClassicFixedBossWaves.EVIL_ADMIN_2, ClassicFixedBossWaves.EVIL_BOSS_1, ClassicFixedBossWaves.EVIL_BOSS_2 ];
|
||||
const evilTeamGrunts = [[ TrainerType.ROCKET_GRUNT ], [ TrainerType.ROCKET_GRUNT ], [ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ], [ TrainerType.GALACTIC_GRUNT ], [ TrainerType.PLASMA_GRUNT ], [ TrainerType.FLARE_GRUNT ], [ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ], [ TrainerType.MACRO_GRUNT ], [ TrainerType.STAR_GRUNT ]];
|
||||
const evilTeamAdmins = [[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [[ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ]], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.COLRESS ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], [ TrainerType.FABA, TrainerType.PLUMERIA ], [ TrainerType.OLEANA ], [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]];
|
||||
const evilTeamBosses = [[ TrainerType.ROCKET_BOSS_GIOVANNI_1 ], [ TrainerType.ROCKET_BOSS_GIOVANNI_1 ], [ TrainerType.MAXIE, TrainerType.ARCHIE ], [ TrainerType.CYRUS ], [ TrainerType.GHETSIS ], [ TrainerType.LYSANDRE ], [ TrainerType.LUSAMINE, TrainerType.GUZMA ], [ TrainerType.ROSE ], [ TrainerType.PENNY ]];
|
||||
const evilTeamBossRematches = [[ TrainerType.ROCKET_BOSS_GIOVANNI_2 ], [ TrainerType.ROCKET_BOSS_GIOVANNI_2 ], [ TrainerType.MAXIE_2, TrainerType.ARCHIE_2 ], [ TrainerType.CYRUS_2 ], [ TrainerType.GHETSIS_2 ], [ TrainerType.LYSANDRE_2 ], [ TrainerType.LUSAMINE_2, TrainerType.GUZMA_2 ], [ TrainerType.ROSE_2 ], [ TrainerType.PENNY_2 ]];
|
||||
const evilTeamWaves: number[] = [
|
||||
ClassicFixedBossWaves.EVIL_GRUNT_1,
|
||||
ClassicFixedBossWaves.EVIL_GRUNT_2,
|
||||
ClassicFixedBossWaves.EVIL_GRUNT_3,
|
||||
ClassicFixedBossWaves.EVIL_ADMIN_1,
|
||||
ClassicFixedBossWaves.EVIL_GRUNT_4,
|
||||
ClassicFixedBossWaves.EVIL_ADMIN_2,
|
||||
ClassicFixedBossWaves.EVIL_BOSS_1,
|
||||
ClassicFixedBossWaves.EVIL_BOSS_2,
|
||||
];
|
||||
const evilTeamGrunts = [
|
||||
[TrainerType.ROCKET_GRUNT],
|
||||
[TrainerType.ROCKET_GRUNT],
|
||||
[TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT],
|
||||
[TrainerType.GALACTIC_GRUNT],
|
||||
[TrainerType.PLASMA_GRUNT],
|
||||
[TrainerType.FLARE_GRUNT],
|
||||
[TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT],
|
||||
[TrainerType.MACRO_GRUNT],
|
||||
[TrainerType.STAR_GRUNT],
|
||||
];
|
||||
const evilTeamAdmins = [
|
||||
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
|
||||
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
|
||||
[
|
||||
[TrainerType.TABITHA, TrainerType.COURTNEY],
|
||||
[TrainerType.MATT, TrainerType.SHELLY],
|
||||
],
|
||||
[TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN],
|
||||
[TrainerType.ZINZOLIN, TrainerType.COLRESS],
|
||||
[TrainerType.XEROSIC, TrainerType.BRYONY],
|
||||
[TrainerType.FABA, TrainerType.PLUMERIA],
|
||||
[TrainerType.OLEANA],
|
||||
[TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI],
|
||||
];
|
||||
const evilTeamBosses = [
|
||||
[TrainerType.ROCKET_BOSS_GIOVANNI_1],
|
||||
[TrainerType.ROCKET_BOSS_GIOVANNI_1],
|
||||
[TrainerType.MAXIE, TrainerType.ARCHIE],
|
||||
[TrainerType.CYRUS],
|
||||
[TrainerType.GHETSIS],
|
||||
[TrainerType.LYSANDRE],
|
||||
[TrainerType.LUSAMINE, TrainerType.GUZMA],
|
||||
[TrainerType.ROSE],
|
||||
[TrainerType.PENNY],
|
||||
];
|
||||
const evilTeamBossRematches = [
|
||||
[TrainerType.ROCKET_BOSS_GIOVANNI_2],
|
||||
[TrainerType.ROCKET_BOSS_GIOVANNI_2],
|
||||
[TrainerType.MAXIE_2, TrainerType.ARCHIE_2],
|
||||
[TrainerType.CYRUS_2],
|
||||
[TrainerType.GHETSIS_2],
|
||||
[TrainerType.LYSANDRE_2],
|
||||
[TrainerType.LUSAMINE_2, TrainerType.GUZMA_2],
|
||||
[TrainerType.ROSE_2],
|
||||
[TrainerType.PENNY_2],
|
||||
];
|
||||
switch (waveIndex) {
|
||||
case ClassicFixedBossWaves.EVIL_GRUNT_1:
|
||||
trainerTypes = evilTeamGrunts[this.value - 1];
|
||||
|
@ -488,42 +560,123 @@ export class SingleGenerationChallenge extends Challenge {
|
|||
break;
|
||||
case ClassicFixedBossWaves.EVIL_BOSS_1:
|
||||
trainerTypes = evilTeamBosses[this.value - 1];
|
||||
battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
|
||||
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false });
|
||||
battleConfig
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
});
|
||||
return true;
|
||||
case ClassicFixedBossWaves.EVIL_BOSS_2:
|
||||
trainerTypes = evilTeamBossRematches[this.value - 1];
|
||||
battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
|
||||
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false });
|
||||
battleConfig
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
|
||||
.setCustomModifierRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
],
|
||||
allowLuckUpgrades: false,
|
||||
});
|
||||
return true;
|
||||
case ClassicFixedBossWaves.ELITE_FOUR_1:
|
||||
trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ];
|
||||
trainerTypes = [
|
||||
TrainerType.LORELEI,
|
||||
TrainerType.WILL,
|
||||
TrainerType.SIDNEY,
|
||||
TrainerType.AARON,
|
||||
TrainerType.SHAUNTAL,
|
||||
TrainerType.MALVA,
|
||||
Utils.randSeedItem([TrainerType.HALA, TrainerType.MOLAYNE]),
|
||||
TrainerType.MARNIE_ELITE,
|
||||
TrainerType.RIKA,
|
||||
];
|
||||
break;
|
||||
case ClassicFixedBossWaves.ELITE_FOUR_2:
|
||||
trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ];
|
||||
trainerTypes = [
|
||||
TrainerType.BRUNO,
|
||||
TrainerType.KOGA,
|
||||
TrainerType.PHOEBE,
|
||||
TrainerType.BERTHA,
|
||||
TrainerType.MARSHAL,
|
||||
TrainerType.SIEBOLD,
|
||||
TrainerType.OLIVIA,
|
||||
TrainerType.NESSA_ELITE,
|
||||
TrainerType.POPPY,
|
||||
];
|
||||
break;
|
||||
case ClassicFixedBossWaves.ELITE_FOUR_3:
|
||||
trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ];
|
||||
trainerTypes = [
|
||||
TrainerType.AGATHA,
|
||||
TrainerType.BRUNO,
|
||||
TrainerType.GLACIA,
|
||||
TrainerType.FLINT,
|
||||
TrainerType.GRIMSLEY,
|
||||
TrainerType.WIKSTROM,
|
||||
TrainerType.ACEROLA,
|
||||
Utils.randSeedItem([TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE]),
|
||||
TrainerType.LARRY_ELITE,
|
||||
];
|
||||
break;
|
||||
case ClassicFixedBossWaves.ELITE_FOUR_4:
|
||||
trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ];
|
||||
trainerTypes = [
|
||||
TrainerType.LANCE,
|
||||
TrainerType.KAREN,
|
||||
TrainerType.DRAKE,
|
||||
TrainerType.LUCIAN,
|
||||
TrainerType.CAITLIN,
|
||||
TrainerType.DRASNA,
|
||||
TrainerType.KAHILI,
|
||||
TrainerType.RAIHAN_ELITE,
|
||||
TrainerType.HASSEL,
|
||||
];
|
||||
break;
|
||||
case ClassicFixedBossWaves.CHAMPION:
|
||||
trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, Utils.randSeedItem([ TrainerType.KUKUI, TrainerType.HAU ]), Utils.randSeedItem([ TrainerType.LEON, TrainerType.MUSTARD ]), Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ];
|
||||
trainerTypes = [
|
||||
TrainerType.BLUE,
|
||||
Utils.randSeedItem([TrainerType.RED, TrainerType.LANCE_CHAMPION]),
|
||||
Utils.randSeedItem([TrainerType.STEVEN, TrainerType.WALLACE]),
|
||||
TrainerType.CYNTHIA,
|
||||
Utils.randSeedItem([TrainerType.ALDER, TrainerType.IRIS]),
|
||||
TrainerType.DIANTHA,
|
||||
Utils.randSeedItem([TrainerType.KUKUI, TrainerType.HAU]),
|
||||
Utils.randSeedItem([TrainerType.LEON, TrainerType.MUSTARD]),
|
||||
Utils.randSeedItem([TrainerType.GEETA, TrainerType.NEMONA]),
|
||||
];
|
||||
break;
|
||||
}
|
||||
if (trainerTypes.length === 0) {
|
||||
return false;
|
||||
} else if (evilTeamWaves.includes(waveIndex)) {
|
||||
battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true));
|
||||
return true;
|
||||
} else if (waveIndex >= ClassicFixedBossWaves.ELITE_FOUR_1 && waveIndex <= ClassicFixedBossWaves.CHAMPION) {
|
||||
const ttypes = trainerTypes as TrainerType[];
|
||||
battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(() => new Trainer(ttypes[this.value - 1], TrainerVariant.DEFAULT));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (evilTeamWaves.includes(waveIndex)) {
|
||||
battleConfig
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
|
||||
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true));
|
||||
return true;
|
||||
}
|
||||
if (waveIndex >= ClassicFixedBossWaves.ELITE_FOUR_1 && waveIndex <= ClassicFixedBossWaves.CHAMPION) {
|
||||
const ttypes = trainerTypes as TrainerType[];
|
||||
battleConfig
|
||||
.setBattleType(BattleType.TRAINER)
|
||||
.setGetTrainerFunc(() => new Trainer(ttypes[this.value - 1], TrainerVariant.DEFAULT));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -556,10 +709,11 @@ export class SingleGenerationChallenge extends Challenge {
|
|||
if (value === 0) {
|
||||
return i18next.t("challenges:singleGeneration.desc_default");
|
||||
}
|
||||
return i18next.t("challenges:singleGeneration.desc", { gen: i18next.t(`challenges:singleGeneration.gen_${value}`) });
|
||||
return i18next.t("challenges:singleGeneration.desc", {
|
||||
gen: i18next.t(`challenges:singleGeneration.gen_${value}`),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static loadChallenge(source: SingleGenerationChallenge | any): SingleGenerationChallenge {
|
||||
const newChallenge = new SingleGenerationChallenge();
|
||||
newChallenge.value = source.value;
|
||||
|
@ -585,17 +739,22 @@ export class SingleTypeChallenge extends Challenge {
|
|||
{ species: Species.CASTFORM, type: PokemonType.NORMAL, fusion: false },
|
||||
];
|
||||
// TODO: Find a solution for all Pokemon with this ssui issue, including Basculin and Burmy
|
||||
private static SPECIES_OVERRIDES: Species[] = [ Species.MELOETTA ];
|
||||
private static SPECIES_OVERRIDES: Species[] = [Species.MELOETTA];
|
||||
|
||||
constructor() {
|
||||
super(Challenges.SINGLE_TYPE, 18);
|
||||
}
|
||||
|
||||
override applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
|
||||
override applyStarterChoice(
|
||||
pokemon: PokemonSpecies,
|
||||
valid: Utils.BooleanHolder,
|
||||
dexAttr: DexAttrProps,
|
||||
soft = false,
|
||||
): boolean {
|
||||
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)) {
|
||||
const speciesToCheck = [ pokemon.speciesId ];
|
||||
const speciesToCheck = [pokemon.speciesId];
|
||||
while (speciesToCheck.length) {
|
||||
const checking = speciesToCheck.pop();
|
||||
if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
|
||||
|
@ -623,8 +782,16 @@ export class SingleTypeChallenge extends Challenge {
|
|||
}
|
||||
|
||||
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
|
||||
if (pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true)
|
||||
&& !SingleTypeChallenge.TYPE_OVERRIDES.some(o => o.type === (this.value - 1) && (pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies! : pokemon.species).speciesId === o.species)) { // TODO: is the bang on fusionSpecies correct?
|
||||
if (
|
||||
pokemon.isPlayer() &&
|
||||
!pokemon.isOfType(this.value - 1, false, false, true) &&
|
||||
!SingleTypeChallenge.TYPE_OVERRIDES.some(
|
||||
o =>
|
||||
o.type === this.value - 1 &&
|
||||
(pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies! : pokemon.species).speciesId === o.species,
|
||||
)
|
||||
) {
|
||||
// TODO: is the bang on fusionSpecies correct?
|
||||
valid.value = false;
|
||||
return true;
|
||||
}
|
||||
|
@ -662,7 +829,9 @@ export class SingleTypeChallenge extends Challenge {
|
|||
const type = i18next.t(`pokemonInfo:Type.${PokemonType[this.value - 1]}`);
|
||||
const typeColor = `[color=${TypeColor[PokemonType[this.value - 1]]}][shadow=${TypeShadow[PokemonType[this.value - 1]]}]${type}[/shadow][/color]`;
|
||||
const defaultDesc = i18next.t("challenges:singleType.desc_default");
|
||||
const typeDesc = i18next.t("challenges:singleType.desc", { type: typeColor });
|
||||
const typeDesc = i18next.t("challenges:singleType.desc", {
|
||||
type: typeColor,
|
||||
});
|
||||
return this.value === 0 ? defaultDesc : typeDesc;
|
||||
}
|
||||
|
||||
|
@ -702,12 +871,17 @@ export class FreshStartChallenge extends Challenge {
|
|||
pokemon.abilityIndex = 0; // Always base ability, not hidden ability
|
||||
pokemon.passive = false; // Passive isn't unlocked
|
||||
pokemon.nature = Nature.HARDY; // Neutral nature
|
||||
pokemon.moveset = pokemon.species.getLevelMoves().filter(m => m[0] <= 5).map(lm => lm[1]).slice(0, 4).map(m => new PokemonMove(m)); // No egg moves
|
||||
pokemon.moveset = pokemon.species
|
||||
.getLevelMoves()
|
||||
.filter(m => m[0] <= 5)
|
||||
.map(lm => lm[1])
|
||||
.slice(0, 4)
|
||||
.map(m => new PokemonMove(m)); // No egg moves
|
||||
pokemon.luck = 0; // No luck
|
||||
pokemon.shiny = false; // Not shiny
|
||||
pokemon.variant = 0; // Not shiny
|
||||
pokemon.formIndex = 0; // Froakie should be base form
|
||||
pokemon.ivs = [ 15, 15, 15, 15, 15, 15 ]; // Default IVs of 15 for all stats (Updated to 15 from 10 in 1.2.0)
|
||||
pokemon.ivs = [15, 15, 15, 15, 15, 15]; // Default IVs of 15 for all stats (Updated to 15 from 10 in 1.2.0)
|
||||
pokemon.teraType = pokemon.species.type1; // Always primary tera type
|
||||
return true;
|
||||
}
|
||||
|
@ -747,7 +921,8 @@ export class InverseBattleChallenge extends Challenge {
|
|||
if (effectiveness.value < 1) {
|
||||
effectiveness.value = 2;
|
||||
return true;
|
||||
} else if (effectiveness.value > 1) {
|
||||
}
|
||||
if (effectiveness.value > 1) {
|
||||
effectiveness.value = 0.5;
|
||||
return true;
|
||||
}
|
||||
|
@ -764,7 +939,7 @@ export class FlipStatChallenge extends Challenge {
|
|||
super(Challenges.FLIP_STAT, 1);
|
||||
}
|
||||
|
||||
override applyFlipStat(pokemon: Pokemon, baseStats: number[]) {
|
||||
override applyFlipStat(_pokemon: Pokemon, baseStats: number[]) {
|
||||
const origStats = Utils.deepCopy(baseStats);
|
||||
baseStats[0] = origStats[5];
|
||||
baseStats[1] = origStats[4];
|
||||
|
@ -858,7 +1033,14 @@ export class LowerStarterPointsChallenge extends Challenge {
|
|||
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_CHOICE, pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.STARTER_CHOICE,
|
||||
pokemon: PokemonSpecies,
|
||||
valid: Utils.BooleanHolder,
|
||||
dexAttr: DexAttrProps,
|
||||
soft: boolean,
|
||||
): boolean;
|
||||
/**
|
||||
* Apply all challenges that modify available total starter points.
|
||||
* @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.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_POINTS, points: Utils.NumberHolder): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.STARTER_POINTS,
|
||||
points: Utils.NumberHolder,
|
||||
): boolean;
|
||||
/**
|
||||
* Apply all challenges that modify the cost of a starter.
|
||||
* @param gameMode {@link GameMode} The current gameMode
|
||||
|
@ -875,7 +1061,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
|
|||
* @param points {@link Utils.NumberHolder} The cost of the pokemon.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_COST, species: Species, cost: Utils.NumberHolder): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.STARTER_COST,
|
||||
species: Species,
|
||||
cost: Utils.NumberHolder,
|
||||
): boolean;
|
||||
/**
|
||||
* Apply all challenges that modify a starter after selection.
|
||||
* @param gameMode {@link GameMode} The current gameMode
|
||||
|
@ -883,7 +1074,11 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
|
|||
* @param pokemon {@link Pokemon} The starter pokemon to modify.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_MODIFY, pokemon: Pokemon): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.STARTER_MODIFY,
|
||||
pokemon: Pokemon,
|
||||
): boolean;
|
||||
/**
|
||||
* Apply all challenges that what pokemon you can have in battle.
|
||||
* @param gameMode {@link GameMode} The current gameMode
|
||||
|
@ -892,7 +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.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.POKEMON_IN_BATTLE, pokemon: Pokemon, valid: Utils.BooleanHolder): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.POKEMON_IN_BATTLE,
|
||||
pokemon: Pokemon,
|
||||
valid: Utils.BooleanHolder,
|
||||
): boolean;
|
||||
/**
|
||||
* Apply all challenges that modify what fixed battles there are.
|
||||
* @param gameMode {@link GameMode} The current gameMode
|
||||
|
@ -901,7 +1101,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
|
|||
* @param battleConfig {@link FixedBattleConfig} The battle config to modify.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FIXED_BATTLES, waveIndex: Number, battleConfig: FixedBattleConfig): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.FIXED_BATTLES,
|
||||
waveIndex: number,
|
||||
battleConfig: FixedBattleConfig,
|
||||
): boolean;
|
||||
/**
|
||||
* Apply all challenges that modify type effectiveness.
|
||||
* @param gameMode {@linkcode GameMode} The current gameMode
|
||||
|
@ -909,7 +1114,11 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
|
|||
* @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.TYPE_EFFECTIVENESS, effectiveness: Utils.NumberHolder): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.TYPE_EFFECTIVENESS,
|
||||
effectiveness: Utils.NumberHolder,
|
||||
): boolean;
|
||||
/**
|
||||
* Apply all challenges that modify what level AI are.
|
||||
* @param gameMode {@link GameMode} The current gameMode
|
||||
|
@ -920,7 +1129,14 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
|
|||
* @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.AI_LEVEL, level: Utils.NumberHolder, levelCap: number, isTrainer: boolean, isBoss: boolean): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.AI_LEVEL,
|
||||
level: Utils.NumberHolder,
|
||||
levelCap: number,
|
||||
isTrainer: boolean,
|
||||
isBoss: boolean,
|
||||
): boolean;
|
||||
/**
|
||||
* Apply all challenges that modify how many move slots the AI has.
|
||||
* @param gameMode {@link GameMode} The current gameMode
|
||||
|
@ -929,7 +1145,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
|
|||
* @param moveSlots {@link Utils.NumberHolder} The amount of move slots.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.AI_MOVE_SLOTS, pokemon: Pokemon, moveSlots: Utils.NumberHolder): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.AI_MOVE_SLOTS,
|
||||
pokemon: Pokemon,
|
||||
moveSlots: Utils.NumberHolder,
|
||||
): boolean;
|
||||
/**
|
||||
* Apply all challenges that modify whether a pokemon has its passive.
|
||||
* @param gameMode {@link GameMode} The current gameMode
|
||||
|
@ -938,7 +1159,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
|
|||
* @param hasPassive {@link Utils.BooleanHolder} Whether it has its passive.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.PASSIVE_ACCESS, pokemon: Pokemon, hasPassive: Utils.BooleanHolder): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.PASSIVE_ACCESS,
|
||||
pokemon: Pokemon,
|
||||
hasPassive: Utils.BooleanHolder,
|
||||
): boolean;
|
||||
/**
|
||||
* Apply all challenges that modify the game modes settings.
|
||||
* @param gameMode {@link GameMode} The current gameMode
|
||||
|
@ -956,7 +1182,14 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
|
|||
* @param level {@link Utils.NumberHolder} The level threshold for access.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_ACCESS, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.NumberHolder): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.MOVE_ACCESS,
|
||||
pokemon: Pokemon,
|
||||
moveSource: MoveSourceType,
|
||||
move: Moves,
|
||||
level: Utils.NumberHolder,
|
||||
): boolean;
|
||||
/**
|
||||
* Apply all challenges that modify what weight a pokemon gives to move generation
|
||||
* @param gameMode {@link GameMode} The current gameMode
|
||||
|
@ -967,9 +1200,21 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
|
|||
* @param weight {@link Utils.NumberHolder} The weight of the move.
|
||||
* @returns True if any challenge was successfully applied.
|
||||
*/
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_WEIGHT, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, weight: Utils.NumberHolder): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.MOVE_WEIGHT,
|
||||
pokemon: Pokemon,
|
||||
moveSource: MoveSourceType,
|
||||
move: Moves,
|
||||
weight: Utils.NumberHolder,
|
||||
): boolean;
|
||||
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FLIP_STAT, pokemon: Pokemon, baseStats: number[]): boolean;
|
||||
export function applyChallenges(
|
||||
gameMode: GameMode,
|
||||
challengeType: ChallengeType.FLIP_STAT,
|
||||
pokemon: Pokemon,
|
||||
baseStats: number[],
|
||||
): boolean;
|
||||
|
||||
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean {
|
||||
let ret = false;
|
||||
|
@ -1057,6 +1302,6 @@ export function initChallenges() {
|
|||
new SingleTypeChallenge(),
|
||||
new FreshStartChallenge(),
|
||||
new InverseBattleChallenge(),
|
||||
new FlipStatChallenge()
|
||||
new FlipStatChallenge(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export interface DailyRunConfig {
|
|||
}
|
||||
|
||||
export function fetchDailyRunSeed(): Promise<string | null> {
|
||||
return new Promise<string | null>((resolve, reject) => {
|
||||
return new Promise<string | null>((resolve, _reject) => {
|
||||
pokerogueApi.daily.getSeed().then(dailySeed => {
|
||||
resolve(dailySeed);
|
||||
});
|
||||
|
@ -26,55 +26,76 @@ export function fetchDailyRunSeed(): Promise<string | null> {
|
|||
export function getDailyRunStarters(seed: string): Starter[] {
|
||||
const starters: Starter[] = [];
|
||||
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
const startingLevel = globalScene.gameMode.getStartingLevel();
|
||||
globalScene.executeWithSeedOffset(
|
||||
() => {
|
||||
const startingLevel = globalScene.gameMode.getStartingLevel();
|
||||
|
||||
if (/\d{18}$/.test(seed)) {
|
||||
for (let s = 0; s < 3; s++) {
|
||||
const offset = 6 + s * 6;
|
||||
const starterSpeciesForm = getPokemonSpeciesForm(parseInt(seed.slice(offset, offset + 4)) as Species, parseInt(seed.slice(offset + 4, offset + 6)));
|
||||
starters.push(getDailyRunStarter(starterSpeciesForm, startingLevel));
|
||||
if (/\d{18}$/.test(seed)) {
|
||||
for (let s = 0; s < 3; s++) {
|
||||
const offset = 6 + s * 6;
|
||||
const starterSpeciesForm = getPokemonSpeciesForm(
|
||||
Number.parseInt(seed.slice(offset, offset + 4)) as Species,
|
||||
Number.parseInt(seed.slice(offset + 4, offset + 6)),
|
||||
);
|
||||
starters.push(getDailyRunStarter(starterSpeciesForm, startingLevel));
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const starterCosts: number[] = [];
|
||||
starterCosts.push(Math.min(Math.round(3.5 + Math.abs(Utils.randSeedGauss(1))), 8));
|
||||
starterCosts.push(Utils.randSeedInt(9 - starterCosts[0], 1));
|
||||
starterCosts.push(10 - (starterCosts[0] + starterCosts[1]));
|
||||
const starterCosts: number[] = [];
|
||||
starterCosts.push(Math.min(Math.round(3.5 + Math.abs(Utils.randSeedGauss(1))), 8));
|
||||
starterCosts.push(Utils.randSeedInt(9 - starterCosts[0], 1));
|
||||
starterCosts.push(10 - (starterCosts[0] + starterCosts[1]));
|
||||
|
||||
for (let c = 0; c < starterCosts.length; c++) {
|
||||
const cost = starterCosts[c];
|
||||
const costSpecies = Object.keys(speciesStarterCosts)
|
||||
.map(s => parseInt(s) as Species)
|
||||
.filter(s => speciesStarterCosts[s] === cost);
|
||||
const randPkmSpecies = getPokemonSpecies(Utils.randSeedItem(costSpecies));
|
||||
const starterSpecies = getPokemonSpecies(randPkmSpecies.getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER));
|
||||
starters.push(getDailyRunStarter(starterSpecies, startingLevel));
|
||||
}
|
||||
}, 0, seed);
|
||||
for (let c = 0; c < starterCosts.length; c++) {
|
||||
const cost = starterCosts[c];
|
||||
const costSpecies = Object.keys(speciesStarterCosts)
|
||||
.map(s => Number.parseInt(s) as Species)
|
||||
.filter(s => speciesStarterCosts[s] === cost);
|
||||
const randPkmSpecies = getPokemonSpecies(Utils.randSeedItem(costSpecies));
|
||||
const starterSpecies = getPokemonSpecies(
|
||||
randPkmSpecies.getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER),
|
||||
);
|
||||
starters.push(getDailyRunStarter(starterSpecies, startingLevel));
|
||||
}
|
||||
},
|
||||
0,
|
||||
seed,
|
||||
);
|
||||
|
||||
return starters;
|
||||
}
|
||||
|
||||
function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLevel: number): Starter {
|
||||
const starterSpecies = starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId);
|
||||
const starterSpecies =
|
||||
starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId);
|
||||
const formIndex = starterSpeciesForm instanceof PokemonSpecies ? undefined : starterSpeciesForm.formIndex;
|
||||
const pokemon = new PlayerPokemon(starterSpecies, startingLevel, undefined, formIndex, undefined, undefined, undefined, undefined, undefined, undefined);
|
||||
const pokemon = new PlayerPokemon(
|
||||
starterSpecies,
|
||||
startingLevel,
|
||||
undefined,
|
||||
formIndex,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
const starter: Starter = {
|
||||
species: starterSpecies,
|
||||
dexAttr: pokemon.getDexAttr(),
|
||||
abilityIndex: pokemon.abilityIndex,
|
||||
passive: false,
|
||||
nature: pokemon.getNature(),
|
||||
pokerus: pokemon.pokerus
|
||||
pokerus: pokemon.pokerus,
|
||||
};
|
||||
pokemon.destroy();
|
||||
return starter;
|
||||
}
|
||||
|
||||
interface BiomeWeights {
|
||||
[key: number]: number
|
||||
[key: number]: number;
|
||||
}
|
||||
|
||||
// Initially weighted by amount of exits each biome has
|
||||
|
|
3053
src/data/dialogue.ts
3053
src/data/dialogue.ts
File diff suppressed because it is too large
Load Diff
|
@ -24,17 +24,17 @@ export class EggHatchData {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the boolean for if the egg move for the hatch is a new unlock
|
||||
* @param unlocked True if the EM is new
|
||||
*/
|
||||
* Sets the boolean for if the egg move for the hatch is a new unlock
|
||||
* @param unlocked True if the EM is new
|
||||
*/
|
||||
setEggMoveUnlocked(unlocked: boolean) {
|
||||
this.eggMoveUnlocked = unlocked;
|
||||
this.eggMoveUnlocked = unlocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a copy of the current DexEntry of the pokemon and StarterDataEntry of its starter
|
||||
* Used before updating the dex, so comparing the pokemon to these entries will show the new attributes
|
||||
*/
|
||||
* Stores a copy of the current DexEntry of the pokemon and StarterDataEntry of its starter
|
||||
* Used before updating the dex, so comparing the pokemon to these entries will show the new attributes
|
||||
*/
|
||||
setDex() {
|
||||
const currDexEntry = globalScene.gameData.dexData[this.pokemon.species.speciesId];
|
||||
const currStarterDataEntry = globalScene.gameData.starterData[this.pokemon.species.getRootSpeciesId()];
|
||||
|
@ -45,7 +45,7 @@ export class EggHatchData {
|
|||
seenCount: currDexEntry.seenCount,
|
||||
caughtCount: currDexEntry.caughtCount,
|
||||
hatchedCount: currDexEntry.hatchedCount,
|
||||
ivs: [ ...currDexEntry.ivs ]
|
||||
ivs: [...currDexEntry.ivs],
|
||||
};
|
||||
this.starterDataEntryBeforeUpdate = {
|
||||
moveset: currStarterDataEntry.moveset,
|
||||
|
@ -55,37 +55,37 @@ export class EggHatchData {
|
|||
abilityAttr: currStarterDataEntry.abilityAttr,
|
||||
passiveAttr: currStarterDataEntry.passiveAttr,
|
||||
valueReduction: currStarterDataEntry.valueReduction,
|
||||
classicWinCount: currStarterDataEntry.classicWinCount
|
||||
classicWinCount: currStarterDataEntry.classicWinCount,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dex entry before update
|
||||
* @returns Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex
|
||||
*/
|
||||
* Gets the dex entry before update
|
||||
* @returns Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex
|
||||
*/
|
||||
getDex(): DexEntry {
|
||||
return this.dexEntryBeforeUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the starter dex entry before update
|
||||
* @returns Starter Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex
|
||||
*/
|
||||
* Gets the starter dex entry before update
|
||||
* @returns Starter Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex
|
||||
*/
|
||||
getStarterEntry(): StarterDataEntry {
|
||||
return this.starterDataEntryBeforeUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the pokedex data corresponding with the new hatch's pokemon data
|
||||
* Also sets whether the egg move is a new unlock or not
|
||||
* @param showMessage boolean to show messages for the new catches and egg moves (false by default)
|
||||
* @returns
|
||||
*/
|
||||
updatePokemon(showMessage : boolean = false) {
|
||||
* Update the pokedex data corresponding with the new hatch's pokemon data
|
||||
* Also sets whether the egg move is a new unlock or not
|
||||
* @param showMessage boolean to show messages for the new catches and egg moves (false by default)
|
||||
* @returns
|
||||
*/
|
||||
updatePokemon(showMessage = false) {
|
||||
return new Promise<void>(resolve => {
|
||||
globalScene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => {
|
||||
globalScene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
|
||||
globalScene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then((value) => {
|
||||
globalScene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then(value => {
|
||||
this.setEggMoveUnlocked(value);
|
||||
resolve();
|
||||
});
|
||||
|
|
166
src/data/egg.ts
166
src/data/egg.ts
|
@ -12,7 +12,31 @@ import i18next from "i18next";
|
|||
import { EggTier } from "#enums/egg-type";
|
||||
import { Species } from "#enums/species";
|
||||
import { EggSourceType } from "#enums/egg-source-types";
|
||||
import { MANAPHY_EGG_MANAPHY_RATE, SAME_SPECIES_EGG_HA_RATE, GACHA_EGG_HA_RATE, GACHA_DEFAULT_RARE_EGGMOVE_RATE, SAME_SPECIES_EGG_RARE_EGGMOVE_RATE, GACHA_MOVE_UP_RARE_EGGMOVE_RATE, GACHA_DEFAULT_SHINY_RATE, GACHA_SHINY_UP_SHINY_RATE, SAME_SPECIES_EGG_SHINY_RATE, EGG_PITY_LEGENDARY_THRESHOLD, EGG_PITY_EPIC_THRESHOLD, EGG_PITY_RARE_THRESHOLD, SHINY_VARIANT_CHANCE, SHINY_EPIC_CHANCE, GACHA_DEFAULT_COMMON_EGG_THRESHOLD, GACHA_DEFAULT_RARE_EGG_THRESHOLD, GACHA_DEFAULT_EPIC_EGG_THRESHOLD, GACHA_LEGENDARY_UP_THRESHOLD_OFFSET, HATCH_WAVES_MANAPHY_EGG, HATCH_WAVES_COMMON_EGG, HATCH_WAVES_RARE_EGG, HATCH_WAVES_EPIC_EGG, HATCH_WAVES_LEGENDARY_EGG } from "#app/data/balance/rates";
|
||||
import {
|
||||
MANAPHY_EGG_MANAPHY_RATE,
|
||||
SAME_SPECIES_EGG_HA_RATE,
|
||||
GACHA_EGG_HA_RATE,
|
||||
GACHA_DEFAULT_RARE_EGGMOVE_RATE,
|
||||
SAME_SPECIES_EGG_RARE_EGGMOVE_RATE,
|
||||
GACHA_MOVE_UP_RARE_EGGMOVE_RATE,
|
||||
GACHA_DEFAULT_SHINY_RATE,
|
||||
GACHA_SHINY_UP_SHINY_RATE,
|
||||
SAME_SPECIES_EGG_SHINY_RATE,
|
||||
EGG_PITY_LEGENDARY_THRESHOLD,
|
||||
EGG_PITY_EPIC_THRESHOLD,
|
||||
EGG_PITY_RARE_THRESHOLD,
|
||||
SHINY_VARIANT_CHANCE,
|
||||
SHINY_EPIC_CHANCE,
|
||||
GACHA_DEFAULT_COMMON_EGG_THRESHOLD,
|
||||
GACHA_DEFAULT_RARE_EGG_THRESHOLD,
|
||||
GACHA_DEFAULT_EPIC_EGG_THRESHOLD,
|
||||
GACHA_LEGENDARY_UP_THRESHOLD_OFFSET,
|
||||
HATCH_WAVES_MANAPHY_EGG,
|
||||
HATCH_WAVES_COMMON_EGG,
|
||||
HATCH_WAVES_RARE_EGG,
|
||||
HATCH_WAVES_EPIC_EGG,
|
||||
HATCH_WAVES_LEGENDARY_EGG,
|
||||
} from "#app/data/balance/rates";
|
||||
import { speciesEggTiers } from "#app/data/balance/species-egg-tiers";
|
||||
|
||||
export const EGG_SEED = 1073741824;
|
||||
|
@ -60,7 +84,6 @@ export interface IEggOptions {
|
|||
}
|
||||
|
||||
export class Egg {
|
||||
|
||||
////
|
||||
// #region Private properties
|
||||
////
|
||||
|
@ -141,7 +164,7 @@ export class Egg {
|
|||
|
||||
this._sourceType = eggOptions?.sourceType!; // TODO: is this bang correct?
|
||||
// Ensure _sourceType is defined before invoking rollEggTier(), as it is referenced
|
||||
this._tier = eggOptions?.tier ?? (Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier());
|
||||
this._tier = eggOptions?.tier ?? Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier();
|
||||
// If egg was pulled, check if egg pity needs to override the egg tier
|
||||
if (eggOptions?.pulled) {
|
||||
// Needs this._tier and this._sourceType to work
|
||||
|
@ -156,7 +179,7 @@ export class Egg {
|
|||
|
||||
// First roll shiny and variant so we can filter if species with an variant exist
|
||||
this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
|
||||
this._variantTier = eggOptions?.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant());
|
||||
this._variantTier = eggOptions?.variantTier ?? Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant();
|
||||
this._species = eggOptions?.species ?? this.rollSpecies()!; // TODO: Is this bang correct?
|
||||
|
||||
this._overrideHiddenAbility = eggOptions?.overrideHiddenAbility ?? false;
|
||||
|
@ -181,9 +204,13 @@ export class Egg {
|
|||
};
|
||||
|
||||
const seedOverride = Utils.randomString(24);
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
generateEggProperties(eggOptions);
|
||||
}, 0, seedOverride);
|
||||
globalScene.executeWithSeedOffset(
|
||||
() => {
|
||||
generateEggProperties(eggOptions);
|
||||
},
|
||||
0,
|
||||
seedOverride,
|
||||
);
|
||||
|
||||
this._eggDescriptor = eggOptions?.eggDescriptor;
|
||||
}
|
||||
|
@ -193,8 +220,11 @@ export class Egg {
|
|||
////
|
||||
|
||||
public isManaphyEgg(): boolean {
|
||||
return (this._species === Species.PHIONE || this._species === Species.MANAPHY) ||
|
||||
this._tier === EggTier.COMMON && !(this._id % 204) && !this._species;
|
||||
return (
|
||||
this._species === Species.PHIONE ||
|
||||
this._species === Species.MANAPHY ||
|
||||
(this._tier === EggTier.COMMON && !(this._id % 204) && !this._species)
|
||||
);
|
||||
}
|
||||
|
||||
public getKey(): string {
|
||||
|
@ -218,14 +248,18 @@ export class Egg {
|
|||
let pokemonSpecies = getPokemonSpecies(this._species);
|
||||
// Special condition to have Phione eggs also have a chance of generating Manaphy
|
||||
if (this._species === Species.PHIONE && this._sourceType === EggSourceType.SAME_SPECIES_EGG) {
|
||||
pokemonSpecies = getPokemonSpecies(Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY);
|
||||
pokemonSpecies = getPokemonSpecies(
|
||||
Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY,
|
||||
);
|
||||
}
|
||||
|
||||
// Sets the hidden ability if a hidden ability exists and
|
||||
// the override is set or the egg hits the chance
|
||||
let abilityIndex: number | undefined = undefined;
|
||||
const sameSpeciesEggHACheck = (this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE));
|
||||
const gachaEggHACheck = (!(this._sourceType === EggSourceType.SAME_SPECIES_EGG) && !Utils.randSeedInt(GACHA_EGG_HA_RATE));
|
||||
const sameSpeciesEggHACheck =
|
||||
this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE);
|
||||
const gachaEggHACheck =
|
||||
!(this._sourceType === EggSourceType.SAME_SPECIES_EGG) && !Utils.randSeedInt(GACHA_EGG_HA_RATE);
|
||||
if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility || sameSpeciesEggHACheck || gachaEggHACheck)) {
|
||||
abilityIndex = 2;
|
||||
}
|
||||
|
@ -242,10 +276,14 @@ export class Egg {
|
|||
}
|
||||
};
|
||||
|
||||
ret = ret!; // Tell TS compiler it's defined now
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
generatePlayerPokemonHelper();
|
||||
}, this._id, EGG_SEED.toString());
|
||||
ret = ret!; // Tell TS compiler it's defined now
|
||||
globalScene.executeWithSeedOffset(
|
||||
() => {
|
||||
generatePlayerPokemonHelper();
|
||||
},
|
||||
this._id,
|
||||
EGG_SEED.toString(),
|
||||
);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -287,9 +325,17 @@ export class Egg {
|
|||
public getEggTypeDescriptor(): string {
|
||||
switch (this.sourceType) {
|
||||
case EggSourceType.SAME_SPECIES_EGG:
|
||||
return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() });
|
||||
return (
|
||||
this._eggDescriptor ??
|
||||
i18next.t("egg:sameSpeciesEgg", {
|
||||
species: getPokemonSpecies(this._species).getName(),
|
||||
})
|
||||
);
|
||||
case EggSourceType.GACHA_LEGENDARY:
|
||||
return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.timestamp)).getName()})`;
|
||||
return (
|
||||
this._eggDescriptor ??
|
||||
`${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.timestamp)).getName()})`
|
||||
);
|
||||
case EggSourceType.GACHA_SHINY:
|
||||
return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny");
|
||||
case EggSourceType.GACHA_MOVE:
|
||||
|
@ -344,9 +390,16 @@ export class Egg {
|
|||
}
|
||||
|
||||
private rollEggTier(): EggTier {
|
||||
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
|
||||
const tierValueOffset =
|
||||
this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
|
||||
const tierValue = Utils.randInt(256);
|
||||
return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.RARE : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.EPIC : EggTier.LEGENDARY;
|
||||
return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset
|
||||
? EggTier.COMMON
|
||||
: tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset
|
||||
? EggTier.RARE
|
||||
: tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset
|
||||
? EggTier.EPIC
|
||||
: EggTier.LEGENDARY;
|
||||
}
|
||||
|
||||
private rollSpecies(): Species | null {
|
||||
|
@ -364,10 +417,10 @@ export class Egg {
|
|||
* when Utils.randSeedInt(8) = 1, and by making the generatePlayerPokemon() species
|
||||
* check pass when Utils.randSeedInt(8) = 0, we can tell them apart during tests.
|
||||
*/
|
||||
const rand = (Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1);
|
||||
const rand = Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1;
|
||||
return rand ? Species.PHIONE : Species.MANAPHY;
|
||||
} else if (this.tier === EggTier.LEGENDARY
|
||||
&& this._sourceType === EggSourceType.GACHA_LEGENDARY) {
|
||||
}
|
||||
if (this.tier === EggTier.LEGENDARY && this._sourceType === EggSourceType.GACHA_LEGENDARY) {
|
||||
if (!Utils.randSeedInt(2)) {
|
||||
return getLegendaryGachaSpeciesForTimestamp(this.timestamp);
|
||||
}
|
||||
|
@ -395,17 +448,25 @@ export class Egg {
|
|||
break;
|
||||
}
|
||||
|
||||
const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ];
|
||||
const ignoredSpecies = [Species.PHIONE, Species.MANAPHY, Species.ETERNATUS];
|
||||
|
||||
let speciesPool = Object.keys(speciesEggTiers)
|
||||
.filter(s => speciesEggTiers[s] === this.tier)
|
||||
.map(s => parseInt(s) as Species)
|
||||
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1);
|
||||
.map(s => Number.parseInt(s) as Species)
|
||||
.filter(
|
||||
s =>
|
||||
!pokemonPrevolutions.hasOwnProperty(s) &&
|
||||
getPokemonSpecies(s).isObtainable() &&
|
||||
ignoredSpecies.indexOf(s) === -1,
|
||||
);
|
||||
|
||||
// If this is the 10th egg without unlocking something new, attempt to force it.
|
||||
if (globalScene.gameData.unlockPity[this.tier] >= 9) {
|
||||
const lockedPool = speciesPool.filter(s => !globalScene.gameData.dexData[s].caughtAttr && !globalScene.gameData.eggs.some(e => e.species === s));
|
||||
if (lockedPool.length) { // Skip this if everything is unlocked
|
||||
const lockedPool = speciesPool.filter(
|
||||
s => !globalScene.gameData.dexData[s].caughtAttr && !globalScene.gameData.eggs.some(e => e.species === s),
|
||||
);
|
||||
if (lockedPool.length) {
|
||||
// Skip this if everything is unlocked
|
||||
speciesPool = lockedPool;
|
||||
}
|
||||
}
|
||||
|
@ -427,11 +488,13 @@ export class Egg {
|
|||
* and being the same each time
|
||||
*/
|
||||
let totalWeight = 0;
|
||||
const speciesWeights : number[] = [];
|
||||
const speciesWeights: number[] = [];
|
||||
for (const speciesId of speciesPool) {
|
||||
// Accounts for species that have starter costs outside of the normal range for their EggTier
|
||||
const speciesCostClamped = Phaser.Math.Clamp(speciesStarterCosts[speciesId], minStarterValue, maxStarterValue);
|
||||
const weight = Math.floor((((maxStarterValue - speciesCostClamped) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
|
||||
const weight = Math.floor(
|
||||
(((maxStarterValue - speciesCostClamped) / (maxStarterValue - minStarterValue + 1)) * 1.5 + 1) * 100,
|
||||
);
|
||||
speciesWeights.push(totalWeight + weight);
|
||||
totalWeight += weight;
|
||||
}
|
||||
|
@ -447,7 +510,10 @@ export class Egg {
|
|||
}
|
||||
species = species!; // tell TS compiled it's defined now!
|
||||
|
||||
if (globalScene.gameData.dexData[species].caughtAttr || globalScene.gameData.eggs.some(e => e.species === species)) {
|
||||
if (
|
||||
globalScene.gameData.dexData[species].caughtAttr ||
|
||||
globalScene.gameData.eggs.some(e => e.species === species)
|
||||
) {
|
||||
globalScene.gameData.unlockPity[this.tier] = Math.min(globalScene.gameData.unlockPity[this.tier] + 1, 10);
|
||||
} else {
|
||||
globalScene.gameData.unlockPity[this.tier] = 0;
|
||||
|
@ -457,9 +523,9 @@ export class Egg {
|
|||
}
|
||||
|
||||
/**
|
||||
* Rolls whether the egg is shiny or not.
|
||||
* @returns `true` if the egg is shiny
|
||||
**/
|
||||
* Rolls whether the egg is shiny or not.
|
||||
* @returns `true` if the egg is shiny
|
||||
**/
|
||||
private rollShiny(): boolean {
|
||||
let shinyChance = GACHA_DEFAULT_SHINY_RATE;
|
||||
switch (this._sourceType) {
|
||||
|
@ -487,20 +553,24 @@ export class Egg {
|
|||
const rand = Utils.randSeedInt(10);
|
||||
if (rand >= SHINY_VARIANT_CHANCE) {
|
||||
return VariantTier.STANDARD; // 6/10
|
||||
} else if (rand >= SHINY_EPIC_CHANCE) {
|
||||
return VariantTier.RARE; // 3/10
|
||||
} else {
|
||||
return VariantTier.EPIC; // 1/10
|
||||
}
|
||||
if (rand >= SHINY_EPIC_CHANCE) {
|
||||
return VariantTier.RARE; // 3/10
|
||||
}
|
||||
return VariantTier.EPIC; // 1/10
|
||||
}
|
||||
|
||||
private checkForPityTierOverrides(): void {
|
||||
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
|
||||
const tierValueOffset =
|
||||
this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
|
||||
globalScene.gameData.eggPity[EggTier.RARE] += 1;
|
||||
globalScene.gameData.eggPity[EggTier.EPIC] += 1;
|
||||
globalScene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset;
|
||||
// These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered.
|
||||
if (globalScene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) {
|
||||
if (
|
||||
globalScene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD &&
|
||||
this._tier === EggTier.COMMON
|
||||
) {
|
||||
this._tier = EggTier.LEGENDARY;
|
||||
} else if (globalScene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) {
|
||||
this._tier = EggTier.EPIC;
|
||||
|
@ -539,10 +609,10 @@ export class Egg {
|
|||
////
|
||||
}
|
||||
|
||||
export function getValidLegendaryGachaSpecies() : Species[] {
|
||||
export function getValidLegendaryGachaSpecies(): Species[] {
|
||||
return Object.entries(speciesEggTiers)
|
||||
.filter(s => s[1] === EggTier.LEGENDARY)
|
||||
.map(s => parseInt(s[0]))
|
||||
.map(s => Number.parseInt(s[0]))
|
||||
.filter(s => getPokemonSpecies(s).isObtainable() && s !== Species.ETERNATUS);
|
||||
}
|
||||
|
||||
|
@ -557,9 +627,13 @@ export function getLegendaryGachaSpeciesForTimestamp(timestamp: number): Species
|
|||
const offset = Math.floor(Math.floor(dayTimestamp / 86400000) / legendarySpecies.length); // Cycle number
|
||||
const index = Math.floor(dayTimestamp / 86400000) % legendarySpecies.length; // Index within cycle
|
||||
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
ret = Phaser.Math.RND.shuffle(legendarySpecies)[index];
|
||||
}, offset, EGG_SEED.toString());
|
||||
globalScene.executeWithSeedOffset(
|
||||
() => {
|
||||
ret = Phaser.Math.RND.shuffle(legendarySpecies)[index];
|
||||
},
|
||||
offset,
|
||||
EGG_SEED.toString(),
|
||||
);
|
||||
ret = ret!; // tell TS compiler it's
|
||||
|
||||
return ret;
|
||||
|
@ -570,6 +644,6 @@ export function getLegendaryGachaSpeciesForTimestamp(timestamp: number): Species
|
|||
* @param pokemonSpecies - Species for wich we will check the egg tier it belongs to
|
||||
* @returns The egg tier of a given pokemon species
|
||||
*/
|
||||
export function getEggTierForSpecies(pokemonSpecies :PokemonSpecies): EggTier {
|
||||
export function getEggTierForSpecies(pokemonSpecies: PokemonSpecies): EggTier {
|
||||
return speciesEggTiers[pokemonSpecies.getRootSpeciesId()];
|
||||
}
|
||||
|
|
|
@ -4,16 +4,64 @@ export enum GrowthRate {
|
|||
MEDIUM_FAST,
|
||||
MEDIUM_SLOW,
|
||||
SLOW,
|
||||
FLUCTUATING
|
||||
FLUCTUATING,
|
||||
}
|
||||
|
||||
const expLevels = [
|
||||
[ 0, 15, 52, 122, 237, 406, 637, 942, 1326, 1800, 2369, 3041, 3822, 4719, 5737, 6881, 8155, 9564, 11111, 12800, 14632, 16610, 18737, 21012, 23437, 26012, 28737, 31610, 34632, 37800, 41111, 44564, 48155, 51881, 55737, 59719, 63822, 68041, 72369, 76800, 81326, 85942, 90637, 95406, 100237, 105122, 110052, 115015, 120001, 125000, 131324, 137795, 144410, 151165, 158056, 165079, 172229, 179503, 186894, 194400, 202013, 209728, 217540, 225443, 233431, 241496, 249633, 257834, 267406, 276458, 286328, 296358, 305767, 316074, 326531, 336255, 346965, 357812, 367807, 378880, 390077, 400293, 411686, 423190, 433572, 445239, 457001, 467489, 479378, 491346, 501878, 513934, 526049, 536557, 548720, 560922, 571333, 583539, 591882, 600000 ],
|
||||
[ 0, 6, 21, 51, 100, 172, 274, 409, 583, 800, 1064, 1382, 1757, 2195, 2700, 3276, 3930, 4665, 5487, 6400, 7408, 8518, 9733, 11059, 12500, 14060, 15746, 17561, 19511, 21600, 23832, 26214, 28749, 31443, 34300, 37324, 40522, 43897, 47455, 51200, 55136, 59270, 63605, 68147, 72900, 77868, 83058, 88473, 94119, 100000, 106120, 112486, 119101, 125971, 133100, 140492, 148154, 156089, 164303, 172800, 181584, 190662, 200037, 209715, 219700, 229996, 240610, 251545, 262807, 274400, 286328, 298598, 311213, 324179, 337500, 351180, 365226, 379641, 394431, 409600, 425152, 441094, 457429, 474163, 491300, 508844, 526802, 545177, 563975, 583200, 602856, 622950, 643485, 664467, 685900, 707788, 730138, 752953, 776239, 800000 ],
|
||||
[ 0, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331, 1728, 2197, 2744, 3375, 4096, 4913, 5832, 6859, 8000, 9261, 10648, 12167, 13824, 15625, 17576, 19683, 21952, 24389, 27000, 29791, 32768, 35937, 39304, 42875, 46656, 50653, 54872, 59319, 64000, 68921, 74088, 79507, 85184, 91125, 97336, 103823, 110592, 117649, 125000, 132651, 140608, 148877, 157464, 166375, 175616, 185193, 195112, 205379, 216000, 226981, 238328, 250047, 262144, 274625, 287496, 300763, 314432, 328509, 343000, 357911, 373248, 389017, 405224, 421875, 438976, 456533, 474552, 493039, 512000, 531441, 551368, 571787, 592704, 614125, 636056, 658503, 681472, 704969, 729000, 753571, 778688, 804357, 830584, 857375, 884736, 912673, 941192, 970299, 1000000 ],
|
||||
[ 0, 9, 57, 96, 135, 179, 236, 314, 419, 560, 742, 973, 1261, 1612, 2035, 2535, 3120, 3798, 4575, 5460, 6458, 7577, 8825, 10208, 11735, 13411, 15244, 17242, 19411, 21760, 24294, 27021, 29949, 33084, 36435, 40007, 43808, 47846, 52127, 56660, 61450, 66505, 71833, 77440, 83335, 89523, 96012, 102810, 109923, 117360, 125126, 133229, 141677, 150476, 159635, 169159, 179056, 189334, 199999, 211060, 222522, 234393, 246681, 259392, 272535, 286115, 300140, 314618, 329555, 344960, 360838, 377197, 394045, 411388, 429235, 447591, 466464, 485862, 505791, 526260, 547274, 568841, 590969, 613664, 636935, 660787, 685228, 710266, 735907, 762160, 789030, 816525, 844653, 873420, 902835, 932903, 963632, 995030, 1027103, 1059860 ],
|
||||
[ 0, 10, 33, 80, 156, 270, 428, 640, 911, 1250, 1663, 2160, 2746, 3430, 4218, 5120, 6141, 7290, 8573, 10000, 11576, 13310, 15208, 17280, 19531, 21970, 24603, 27440, 30486, 33750, 37238, 40960, 44921, 49130, 53593, 58320, 63316, 68590, 74148, 80000, 86151, 92610, 99383, 106480, 113906, 121670, 129778, 138240, 147061, 156250, 165813, 175760, 186096, 196830, 207968, 219520, 231491, 243890, 256723, 270000, 283726, 297910, 312558, 327680, 343281, 359370, 375953, 393040, 410636, 428750, 447388, 466560, 486271, 506530, 527343, 548720, 570666, 593190, 616298, 640000, 664301, 689210, 714733, 740880, 767656, 795070, 823128, 851840, 881211, 911250, 941963, 973360, 1005446, 1038230, 1071718, 1105920, 1140841, 1176490, 1212873, 1250000 ],
|
||||
[ 0, 4, 13, 32, 65, 112, 178, 276, 393, 540, 745, 967, 1230, 1591, 1957, 2457, 3046, 3732, 4526, 5440, 6482, 7666, 9003, 10506, 12187, 14060, 16140, 18439, 20974, 23760, 26811, 30146, 33780, 37731, 42017, 46656, 50653, 55969, 60505, 66560, 71677, 78533, 84277, 91998, 98415, 107069, 114205, 123863, 131766, 142500, 151222, 163105, 172697, 185807, 196322, 210739, 222231, 238036, 250562, 267840, 281456, 300293, 315059, 335544, 351520, 373744, 390991, 415050, 433631, 459620, 479600, 507617, 529063, 559209, 582187, 614566, 639146, 673863, 700115, 737280, 765275, 804997, 834809, 877201, 908905, 954084, 987754, 1035837, 1071552, 1122660, 1160499, 1214753, 1254796, 1312322, 1354652, 1415577, 1460276, 1524731, 1571884, 1640000 ]
|
||||
[
|
||||
0, 15, 52, 122, 237, 406, 637, 942, 1326, 1800, 2369, 3041, 3822, 4719, 5737, 6881, 8155, 9564, 11111, 12800, 14632,
|
||||
16610, 18737, 21012, 23437, 26012, 28737, 31610, 34632, 37800, 41111, 44564, 48155, 51881, 55737, 59719, 63822,
|
||||
68041, 72369, 76800, 81326, 85942, 90637, 95406, 100237, 105122, 110052, 115015, 120001, 125000, 131324, 137795,
|
||||
144410, 151165, 158056, 165079, 172229, 179503, 186894, 194400, 202013, 209728, 217540, 225443, 233431, 241496,
|
||||
249633, 257834, 267406, 276458, 286328, 296358, 305767, 316074, 326531, 336255, 346965, 357812, 367807, 378880,
|
||||
390077, 400293, 411686, 423190, 433572, 445239, 457001, 467489, 479378, 491346, 501878, 513934, 526049, 536557,
|
||||
548720, 560922, 571333, 583539, 591882, 600000,
|
||||
],
|
||||
[
|
||||
0, 6, 21, 51, 100, 172, 274, 409, 583, 800, 1064, 1382, 1757, 2195, 2700, 3276, 3930, 4665, 5487, 6400, 7408, 8518,
|
||||
9733, 11059, 12500, 14060, 15746, 17561, 19511, 21600, 23832, 26214, 28749, 31443, 34300, 37324, 40522, 43897,
|
||||
47455, 51200, 55136, 59270, 63605, 68147, 72900, 77868, 83058, 88473, 94119, 100000, 106120, 112486, 119101, 125971,
|
||||
133100, 140492, 148154, 156089, 164303, 172800, 181584, 190662, 200037, 209715, 219700, 229996, 240610, 251545,
|
||||
262807, 274400, 286328, 298598, 311213, 324179, 337500, 351180, 365226, 379641, 394431, 409600, 425152, 441094,
|
||||
457429, 474163, 491300, 508844, 526802, 545177, 563975, 583200, 602856, 622950, 643485, 664467, 685900, 707788,
|
||||
730138, 752953, 776239, 800000,
|
||||
],
|
||||
[
|
||||
0, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331, 1728, 2197, 2744, 3375, 4096, 4913, 5832, 6859, 8000, 9261,
|
||||
10648, 12167, 13824, 15625, 17576, 19683, 21952, 24389, 27000, 29791, 32768, 35937, 39304, 42875, 46656, 50653,
|
||||
54872, 59319, 64000, 68921, 74088, 79507, 85184, 91125, 97336, 103823, 110592, 117649, 125000, 132651, 140608,
|
||||
148877, 157464, 166375, 175616, 185193, 195112, 205379, 216000, 226981, 238328, 250047, 262144, 274625, 287496,
|
||||
300763, 314432, 328509, 343000, 357911, 373248, 389017, 405224, 421875, 438976, 456533, 474552, 493039, 512000,
|
||||
531441, 551368, 571787, 592704, 614125, 636056, 658503, 681472, 704969, 729000, 753571, 778688, 804357, 830584,
|
||||
857375, 884736, 912673, 941192, 970299, 1000000,
|
||||
],
|
||||
[
|
||||
0, 9, 57, 96, 135, 179, 236, 314, 419, 560, 742, 973, 1261, 1612, 2035, 2535, 3120, 3798, 4575, 5460, 6458, 7577,
|
||||
8825, 10208, 11735, 13411, 15244, 17242, 19411, 21760, 24294, 27021, 29949, 33084, 36435, 40007, 43808, 47846,
|
||||
52127, 56660, 61450, 66505, 71833, 77440, 83335, 89523, 96012, 102810, 109923, 117360, 125126, 133229, 141677,
|
||||
150476, 159635, 169159, 179056, 189334, 199999, 211060, 222522, 234393, 246681, 259392, 272535, 286115, 300140,
|
||||
314618, 329555, 344960, 360838, 377197, 394045, 411388, 429235, 447591, 466464, 485862, 505791, 526260, 547274,
|
||||
568841, 590969, 613664, 636935, 660787, 685228, 710266, 735907, 762160, 789030, 816525, 844653, 873420, 902835,
|
||||
932903, 963632, 995030, 1027103, 1059860,
|
||||
],
|
||||
[
|
||||
0, 10, 33, 80, 156, 270, 428, 640, 911, 1250, 1663, 2160, 2746, 3430, 4218, 5120, 6141, 7290, 8573, 10000, 11576,
|
||||
13310, 15208, 17280, 19531, 21970, 24603, 27440, 30486, 33750, 37238, 40960, 44921, 49130, 53593, 58320, 63316,
|
||||
68590, 74148, 80000, 86151, 92610, 99383, 106480, 113906, 121670, 129778, 138240, 147061, 156250, 165813, 175760,
|
||||
186096, 196830, 207968, 219520, 231491, 243890, 256723, 270000, 283726, 297910, 312558, 327680, 343281, 359370,
|
||||
375953, 393040, 410636, 428750, 447388, 466560, 486271, 506530, 527343, 548720, 570666, 593190, 616298, 640000,
|
||||
664301, 689210, 714733, 740880, 767656, 795070, 823128, 851840, 881211, 911250, 941963, 973360, 1005446, 1038230,
|
||||
1071718, 1105920, 1140841, 1176490, 1212873, 1250000,
|
||||
],
|
||||
[
|
||||
0, 4, 13, 32, 65, 112, 178, 276, 393, 540, 745, 967, 1230, 1591, 1957, 2457, 3046, 3732, 4526, 5440, 6482, 7666,
|
||||
9003, 10506, 12187, 14060, 16140, 18439, 20974, 23760, 26811, 30146, 33780, 37731, 42017, 46656, 50653, 55969,
|
||||
60505, 66560, 71677, 78533, 84277, 91998, 98415, 107069, 114205, 123863, 131766, 142500, 151222, 163105, 172697,
|
||||
185807, 196322, 210739, 222231, 238036, 250562, 267840, 281456, 300293, 315059, 335544, 351520, 373744, 390991,
|
||||
415050, 433631, 459620, 479600, 507617, 529063, 559209, 582187, 614566, 639146, 673863, 700115, 737280, 765275,
|
||||
804997, 834809, 877201, 908905, 954084, 987754, 1035837, 1071552, 1122660, 1160499, 1214753, 1254796, 1312322,
|
||||
1354652, 1415577, 1460276, 1524731, 1571884, 1640000,
|
||||
],
|
||||
];
|
||||
|
||||
export function getLevelTotalExp(level: number, growthRate: GrowthRate): number {
|
||||
|
@ -29,22 +77,22 @@ export function getLevelTotalExp(level: number, growthRate: GrowthRate): number
|
|||
|
||||
switch (growthRate) {
|
||||
case GrowthRate.ERRATIC:
|
||||
ret = (Math.pow(level, 4) + (Math.pow(level, 3) * 2000)) / 3500;
|
||||
ret = (Math.pow(level, 4) + Math.pow(level, 3) * 2000) / 3500;
|
||||
break;
|
||||
case GrowthRate.FAST:
|
||||
ret = Math.pow(level, 3) * 4 / 5;
|
||||
ret = (Math.pow(level, 3) * 4) / 5;
|
||||
break;
|
||||
case GrowthRate.MEDIUM_FAST:
|
||||
ret = Math.pow(level, 3);
|
||||
break;
|
||||
case GrowthRate.MEDIUM_SLOW:
|
||||
ret = (Math.pow(level, 3) * 6 / 5) - (15 * Math.pow(level, 2)) + (100 * level) - 140;
|
||||
ret = (Math.pow(level, 3) * 6) / 5 - 15 * Math.pow(level, 2) + 100 * level - 140;
|
||||
break;
|
||||
case GrowthRate.SLOW:
|
||||
ret = Math.pow(level, 3) * 5 / 4;
|
||||
ret = (Math.pow(level, 3) * 5) / 4;
|
||||
break;
|
||||
case GrowthRate.FLUCTUATING:
|
||||
ret = (Math.pow(level, 3) * ((level / 2) + 8)) * 4 / (100 + level);
|
||||
ret = (Math.pow(level, 3) * (level / 2 + 8) * 4) / (100 + level);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export enum Gender {
|
||||
GENDERLESS = -1,
|
||||
MALE,
|
||||
FEMALE
|
||||
GENDERLESS = -1,
|
||||
MALE,
|
||||
FEMALE,
|
||||
}
|
||||
|
||||
export function getGenderSymbol(gender: Gender) {
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { globalScene } from "#app/global-scene";
|
||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { trainerConfigs, } from "#app/data/trainer-config";
|
||||
import {
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { trainerConfigs } from "#app/data/trainer-config";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
|
@ -27,161 +32,172 @@ const namespace = "mysteryEncounters/aTrainersTest";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3816 | GitHub Issue #3816}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const ATrainersTestEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||
.withIntroDialogue([
|
||||
export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.A_TRAINERS_TEST,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Randomly pick from 1 of the 5 stat trainers to spawn
|
||||
let trainerType: TrainerType;
|
||||
let spriteKeys: { spriteKey: any; fileRoot: any };
|
||||
let trainerNameKey: string;
|
||||
switch (randSeedInt(5)) {
|
||||
case 1:
|
||||
trainerType = TrainerType.CHERYL;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY);
|
||||
trainerNameKey = "cheryl";
|
||||
break;
|
||||
case 2:
|
||||
trainerType = TrainerType.MARLEY;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE);
|
||||
trainerNameKey = "marley";
|
||||
break;
|
||||
case 3:
|
||||
trainerType = TrainerType.MIRA;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1);
|
||||
trainerNameKey = "mira";
|
||||
break;
|
||||
case 4:
|
||||
trainerType = TrainerType.RILEY;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1);
|
||||
trainerNameKey = "riley";
|
||||
break;
|
||||
default:
|
||||
trainerType = TrainerType.BUCK;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL);
|
||||
trainerNameKey = "buck";
|
||||
break;
|
||||
}
|
||||
|
||||
// Dialogue and tokens for trainer
|
||||
encounter.dialogue.intro = [
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
speaker: `trainerNames:${trainerNameKey}`,
|
||||
text: `${namespace}:${trainerNameKey}.intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withOnInit(() => {
|
||||
];
|
||||
encounter.options[0].dialogue!.selected = [
|
||||
{
|
||||
speaker: `trainerNames:${trainerNameKey}`,
|
||||
text: `${namespace}:${trainerNameKey}.accept`,
|
||||
},
|
||||
];
|
||||
encounter.options[1].dialogue!.selected = [
|
||||
{
|
||||
speaker: `trainerNames:${trainerNameKey}`,
|
||||
text: `${namespace}:${trainerNameKey}.decline`,
|
||||
},
|
||||
];
|
||||
|
||||
encounter.setDialogueToken("statTrainerName", i18next.t(`trainerNames:${trainerNameKey}`));
|
||||
const eggDescription = `${i18next.t(`${namespace}:title`)}:\n${i18next.t(`trainerNames:${trainerNameKey}`)}`;
|
||||
encounter.misc = {
|
||||
trainerType,
|
||||
trainerNameKey,
|
||||
trainerEggDescription: eggDescription,
|
||||
};
|
||||
|
||||
// Trainer config
|
||||
const trainerConfig = trainerConfigs[trainerType].clone();
|
||||
const trainerSpriteKey = trainerConfig.getSpriteKey();
|
||||
encounter.enemyPartyConfigs.push({
|
||||
levelAdditiveModifier: 1,
|
||||
trainerConfig: trainerConfig,
|
||||
});
|
||||
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: spriteKeys.spriteKey,
|
||||
fileRoot: spriteKeys.fileRoot,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
x: 22,
|
||||
y: -2,
|
||||
yShadow: -2,
|
||||
},
|
||||
{
|
||||
spriteKey: trainerSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
disableAnimation: true,
|
||||
x: -24,
|
||||
y: 4,
|
||||
yShadow: 4,
|
||||
},
|
||||
];
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withIntroDialogue()
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Battle the stat trainer for an Egg and great rewards
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
// Randomly pick from 1 of the 5 stat trainers to spawn
|
||||
let trainerType: TrainerType;
|
||||
let spriteKeys;
|
||||
let trainerNameKey: string;
|
||||
switch (randSeedInt(5)) {
|
||||
default:
|
||||
case 0:
|
||||
trainerType = TrainerType.BUCK;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL);
|
||||
trainerNameKey = "buck";
|
||||
break;
|
||||
case 1:
|
||||
trainerType = TrainerType.CHERYL;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY);
|
||||
trainerNameKey = "cheryl";
|
||||
break;
|
||||
case 2:
|
||||
trainerType = TrainerType.MARLEY;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE);
|
||||
trainerNameKey = "marley";
|
||||
break;
|
||||
case 3:
|
||||
trainerType = TrainerType.MIRA;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1);
|
||||
trainerNameKey = "mira";
|
||||
break;
|
||||
case 4:
|
||||
trainerType = TrainerType.RILEY;
|
||||
spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1);
|
||||
trainerNameKey = "riley";
|
||||
break;
|
||||
}
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
|
||||
// Dialogue and tokens for trainer
|
||||
encounter.dialogue.intro = [
|
||||
const eggOptions: IEggOptions = {
|
||||
pulled: false,
|
||||
sourceType: EggSourceType.EVENT,
|
||||
eggDescriptor: encounter.misc.trainerEggDescription,
|
||||
tier: EggTier.EPIC,
|
||||
};
|
||||
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
|
||||
setEncounterRewards(
|
||||
{
|
||||
speaker: `trainerNames:${trainerNameKey}`,
|
||||
text: `${namespace}:${trainerNameKey}.intro_dialogue`
|
||||
}
|
||||
];
|
||||
encounter.options[0].dialogue!.selected = [
|
||||
{
|
||||
speaker: `trainerNames:${trainerNameKey}`,
|
||||
text: `${namespace}:${trainerNameKey}.accept`
|
||||
}
|
||||
];
|
||||
encounter.options[1].dialogue!.selected = [
|
||||
{
|
||||
speaker: `trainerNames:${trainerNameKey}`,
|
||||
text: `${namespace}:${trainerNameKey}.decline`
|
||||
}
|
||||
];
|
||||
|
||||
encounter.setDialogueToken("statTrainerName", i18next.t(`trainerNames:${trainerNameKey}`));
|
||||
const eggDescription = i18next.t(`${namespace}:title`) + ":\n" + i18next.t(`trainerNames:${trainerNameKey}`);
|
||||
encounter.misc = { trainerType, trainerNameKey, trainerEggDescription: eggDescription };
|
||||
|
||||
// Trainer config
|
||||
const trainerConfig = trainerConfigs[trainerType].clone();
|
||||
const trainerSpriteKey = trainerConfig.getSpriteKey();
|
||||
encounter.enemyPartyConfigs.push({
|
||||
levelAdditiveModifier: 1,
|
||||
trainerConfig: trainerConfig
|
||||
});
|
||||
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: spriteKeys.spriteKey,
|
||||
fileRoot: spriteKeys.fileRoot,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
x: 22,
|
||||
y: -2,
|
||||
yShadow: -2
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SACRED_ASH],
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA],
|
||||
fillRemaining: true,
|
||||
},
|
||||
{
|
||||
spriteKey: trainerSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
disableAnimation: true,
|
||||
x: -24,
|
||||
y: 4,
|
||||
yShadow: 4
|
||||
}
|
||||
];
|
||||
[eggOptions],
|
||||
);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
},
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Full heal party
|
||||
globalScene.unshiftPhase(new PartyHealPhase(true));
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withIntroDialogue()
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Battle the stat trainer for an Egg and great rewards
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
|
||||
const eggOptions: IEggOptions = {
|
||||
pulled: false,
|
||||
sourceType: EggSourceType.EVENT,
|
||||
eggDescriptor: encounter.misc.trainerEggDescription,
|
||||
tier: EggTier.EPIC
|
||||
};
|
||||
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Full heal party
|
||||
globalScene.unshiftPhase(new PartyHealPhase(true));
|
||||
|
||||
const eggOptions: IEggOptions = {
|
||||
pulled: false,
|
||||
sourceType: EggSourceType.EVENT,
|
||||
eggDescriptor: encounter.misc.trainerEggDescription,
|
||||
tier: EggTier.RARE
|
||||
};
|
||||
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`));
|
||||
setEncounterRewards({ fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]);
|
||||
leaveEncounterWithoutBattle();
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`
|
||||
}
|
||||
])
|
||||
.build();
|
||||
const eggOptions: IEggOptions = {
|
||||
pulled: false,
|
||||
sourceType: EggSourceType.EVENT,
|
||||
eggDescriptor: encounter.misc.trainerEggDescription,
|
||||
tier: EggTier.RARE,
|
||||
};
|
||||
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`));
|
||||
setEncounterRewards({ fillRemaining: false, rerollMultiplier: -1 }, [eggOptions]);
|
||||
leaveEncounterWithoutBattle();
|
||||
},
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
|
@ -20,7 +26,11 @@ import { Moves } from "#enums/moves";
|
|||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { randInt } from "#app/utils";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
catchPokemon,
|
||||
getHighestLevelPlayerPokemon,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
||||
|
@ -38,339 +48,348 @@ const namespace = "mysteryEncounters/absoluteAvarice";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3805 | GitHub Issue #3805}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.ABSOLUTE_AVARICE)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Must have at least 4 berries to spawn
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
// This sprite has the shadow
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.GREEDENT,
|
||||
hasShadow: true,
|
||||
alpha: 0.001,
|
||||
repeat: true,
|
||||
x: -5
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.GREEDENT,
|
||||
hasShadow: false,
|
||||
repeat: true,
|
||||
x: -5
|
||||
},
|
||||
{
|
||||
spriteKey: "lum_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: -14,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "salac_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 2,
|
||||
y: 4,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "lansat_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 32,
|
||||
y: 5,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "liechi_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 6,
|
||||
y: -5,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "sitrus_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: 8,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "enigma_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 26,
|
||||
y: -4,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "leppa_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -27,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "petaya_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 30,
|
||||
y: -17,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "ganlon_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -11,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "apicot_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 14,
|
||||
y: -2,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: "starf_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 18,
|
||||
y: 9,
|
||||
hidden: true,
|
||||
disableAnimation: true
|
||||
},
|
||||
])
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Must have at least 4 berries to spawn
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
// This sprite has the shadow
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.GREEDENT,
|
||||
hasShadow: true,
|
||||
alpha: 0.001,
|
||||
repeat: true,
|
||||
x: -5,
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.GREEDENT,
|
||||
hasShadow: false,
|
||||
repeat: true,
|
||||
x: -5,
|
||||
},
|
||||
{
|
||||
spriteKey: "lum_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: -14,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "salac_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 2,
|
||||
y: 4,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "lansat_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 32,
|
||||
y: 5,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "liechi_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 6,
|
||||
y: -5,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "sitrus_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 7,
|
||||
y: 8,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "enigma_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 26,
|
||||
y: -4,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "leppa_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -27,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "petaya_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 30,
|
||||
y: -17,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "ganlon_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 16,
|
||||
y: -11,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "apicot_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 14,
|
||||
y: -2,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "starf_berry",
|
||||
fileRoot: "items",
|
||||
isItem: true,
|
||||
x: 18,
|
||||
y: 9,
|
||||
hidden: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
])
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
|
||||
globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
|
||||
|
||||
// Get all player berry items, remove from party, and store reference
|
||||
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
|
||||
// Sort berries by party member ID to more easily re-add later if necessary
|
||||
const berryItemsMap = new Map<number, BerryModifier[]>();
|
||||
globalScene.getPlayerParty().forEach(pokemon => {
|
||||
const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id);
|
||||
if (pokemonBerries?.length > 0) {
|
||||
berryItemsMap.set(pokemon.id, pokemonBerries);
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
});
|
||||
|
||||
globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
|
||||
globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
|
||||
encounter.misc = { berryItemsMap };
|
||||
|
||||
// Get all player berry items, remove from party, and store reference
|
||||
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
// Generates copies of the stolen berries to put on the Greedent
|
||||
const bossModifierConfigs: HeldModifierConfig[] = [];
|
||||
berryItems.forEach(berryMod => {
|
||||
// Can't define stack count on a ModifierType, have to just create separate instances for each stack
|
||||
// Overflow berries will be "lost" on the boss, but it's un-catchable anyway
|
||||
for (let i = 0; i < berryMod.stackCount; i++) {
|
||||
const modifierType = generateModifierType(modifierTypes.BERRY, [
|
||||
berryMod.berryType,
|
||||
]) as PokemonHeldItemModifierType;
|
||||
bossModifierConfigs.push({ modifier: modifierType });
|
||||
}
|
||||
});
|
||||
|
||||
// Sort berries by party member ID to more easily re-add later if necessary
|
||||
const berryItemsMap = new Map<number, BerryModifier[]>();
|
||||
globalScene.getPlayerParty().forEach(pokemon => {
|
||||
const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id);
|
||||
if (pokemonBerries?.length > 0) {
|
||||
berryItemsMap.set(pokemon.id, pokemonBerries);
|
||||
}
|
||||
});
|
||||
// Do NOT remove the real berries yet or else it will be persisted in the session data
|
||||
|
||||
encounter.misc = { berryItemsMap };
|
||||
// SpDef buff below wave 50, +1 to all stats otherwise
|
||||
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
|
||||
globalScene.currentBattle.waveIndex < 50 ? [Stat.SPDEF] : [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
|
||||
|
||||
// Generates copies of the stolen berries to put on the Greedent
|
||||
const bossModifierConfigs: HeldModifierConfig[] = [];
|
||||
berryItems.forEach(berryMod => {
|
||||
// Can't define stack count on a ModifierType, have to just create separate instances for each stack
|
||||
// Overflow berries will be "lost" on the boss, but it's un-catchable anyway
|
||||
for (let i = 0; i < berryMod.stackCount; i++) {
|
||||
const modifierType = generateModifierType(modifierTypes.BERRY, [ berryMod.berryType ]) as PokemonHeldItemModifierType;
|
||||
bossModifierConfigs.push({ modifier: modifierType });
|
||||
}
|
||||
});
|
||||
// Calculate boss mon
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveModifier: 1,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.GREEDENT),
|
||||
isBoss: true,
|
||||
bossSegments: 3,
|
||||
shiny: false, // Shiny lock because of consistency issues between the different options
|
||||
moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH],
|
||||
modifierConfigs: bossModifierConfigs,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
|
||||
globalScene.unshiftPhase(
|
||||
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1),
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Do NOT remove the real berries yet or else it will be persisted in the session data
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
encounter.setDialogueToken("greedentName", getPokemonSpecies(Species.GREEDENT).getName());
|
||||
|
||||
// SpDef buff below wave 50, +1 to all stats otherwise
|
||||
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
|
||||
[ Stat.SPDEF ] :
|
||||
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
doGreedentSpriteSteal();
|
||||
doBerrySpritePile();
|
||||
|
||||
// Calculate boss mon
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveModifier: 1,
|
||||
pokemonConfigs: [
|
||||
// Remove the berries from the party
|
||||
// Session has been safely saved at this point, so data won't be lost
|
||||
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
berryItems.forEach(berryMod => {
|
||||
globalScene.removeModifier(berryMod);
|
||||
});
|
||||
|
||||
globalScene.updateModifiers(true);
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.GREEDENT),
|
||||
isBoss: true,
|
||||
bossSegments: 3,
|
||||
shiny: false, // Shiny lock because of consistency issues between the different options
|
||||
moveSet: [ Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH ],
|
||||
modifierConfigs: bossModifierConfigs,
|
||||
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Provides 1x Reviver Seed to each party member at end of battle
|
||||
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED);
|
||||
encounter.setDialogueToken(
|
||||
"foodReward",
|
||||
revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"),
|
||||
);
|
||||
const givePartyPokemonReviverSeeds = () => {
|
||||
const party = globalScene.getPlayerParty();
|
||||
party.forEach(p => {
|
||||
const heldItems = p.getHeldItems();
|
||||
if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) {
|
||||
const seedModifier = revSeed.newModifier(p);
|
||||
globalScene.addModifier(seedModifier, false, false, false, true);
|
||||
}
|
||||
});
|
||||
queueEncounterMessage(`${namespace}:option.1.food_stash`);
|
||||
};
|
||||
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, givePartyPokemonReviverSeeds);
|
||||
encounter.startOfBattleEffects.push({
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.ENEMY],
|
||||
move: new PokemonMove(Moves.STUFF_CHEEKS),
|
||||
ignorePp: true,
|
||||
});
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const berryMap = encounter.misc.berryItemsMap;
|
||||
|
||||
// Returns 2/5 of the berries stolen to each Pokemon
|
||||
const party = globalScene.getPlayerParty();
|
||||
party.forEach(pokemon => {
|
||||
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
|
||||
const berryTypesAsArray: BerryType[] = [];
|
||||
stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType)));
|
||||
const returnedBerryCount = Math.floor(((berryTypesAsArray.length ?? 0) * 2) / 5);
|
||||
|
||||
if (returnedBerryCount > 0) {
|
||||
for (let i = 0; i < returnedBerryCount; i++) {
|
||||
// Shuffle remaining berry types and pop
|
||||
Phaser.Math.RND.shuffle(berryTypesAsArray);
|
||||
const randBerryType = berryTypesAsArray.pop();
|
||||
|
||||
const berryModType = generateModifierType(modifierTypes.BERRY, [randBerryType]) as BerryModifierType;
|
||||
applyModifierTypeToPlayerPokemon(pokemon, berryModType);
|
||||
}
|
||||
}
|
||||
});
|
||||
await globalScene.updateModifiers(true);
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
};
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Animate berries being eaten
|
||||
doGreedentEatBerries();
|
||||
doBerrySpritePile(true);
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Let it have the food
|
||||
// Greedent joins the team, level equal to 2 below highest party member (shiny locked)
|
||||
const level = getHighestLevelPlayerPokemon(false, true).level - 2;
|
||||
const greedent = new EnemyPokemon(getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true);
|
||||
greedent.moveset = [
|
||||
new PokemonMove(Moves.THRASH),
|
||||
new PokemonMove(Moves.BODY_PRESS),
|
||||
new PokemonMove(Moves.STUFF_CHEEKS),
|
||||
new PokemonMove(Moves.SLACK_OFF),
|
||||
];
|
||||
greedent.passive = true;
|
||||
|
||||
encounter.enemyPartyConfigs = [ config ];
|
||||
encounter.setDialogueToken("greedentName", getPokemonSpecies(Species.GREEDENT).getName());
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
doGreedentSpriteSteal();
|
||||
doBerrySpritePile();
|
||||
|
||||
// Remove the berries from the party
|
||||
// Session has been safely saved at this point, so data won't be lost
|
||||
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
berryItems.forEach(berryMod => {
|
||||
globalScene.removeModifier(berryMod);
|
||||
});
|
||||
|
||||
globalScene.updateModifiers(true);
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Provides 1x Reviver Seed to each party member at end of battle
|
||||
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED);
|
||||
encounter.setDialogueToken("foodReward", revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"));
|
||||
const givePartyPokemonReviverSeeds = () => {
|
||||
const party = globalScene.getPlayerParty();
|
||||
party.forEach(p => {
|
||||
const heldItems = p.getHeldItems();
|
||||
if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) {
|
||||
const seedModifier = revSeed.newModifier(p);
|
||||
globalScene.addModifier(seedModifier, false, false, false, true);
|
||||
}
|
||||
});
|
||||
queueEncounterMessage(`${namespace}:option.1.food_stash`);
|
||||
};
|
||||
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, givePartyPokemonReviverSeeds);
|
||||
encounter.startOfBattleEffects.push({
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [ BattlerIndex.ENEMY ],
|
||||
move: new PokemonMove(Moves.STUFF_CHEEKS),
|
||||
ignorePp: true
|
||||
});
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const berryMap = encounter.misc.berryItemsMap;
|
||||
|
||||
// Returns 2/5 of the berries stolen to each Pokemon
|
||||
const party = globalScene.getPlayerParty();
|
||||
party.forEach(pokemon => {
|
||||
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
|
||||
const berryTypesAsArray: BerryType[] = [];
|
||||
stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType)));
|
||||
const returnedBerryCount = Math.floor((berryTypesAsArray.length ?? 0) * 2 / 5);
|
||||
|
||||
if (returnedBerryCount > 0) {
|
||||
for (let i = 0; i < returnedBerryCount; i++) {
|
||||
// Shuffle remaining berry types and pop
|
||||
Phaser.Math.RND.shuffle(berryTypesAsArray);
|
||||
const randBerryType = berryTypesAsArray.pop();
|
||||
|
||||
const berryModType = generateModifierType(modifierTypes.BERRY, [ randBerryType ]) as BerryModifierType;
|
||||
applyModifierTypeToPlayerPokemon(pokemon, berryModType);
|
||||
}
|
||||
}
|
||||
});
|
||||
await globalScene.updateModifiers(true);
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Animate berries being eaten
|
||||
doGreedentEatBerries();
|
||||
doBerrySpritePile(true);
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Let it have the food
|
||||
// Greedent joins the team, level equal to 2 below highest party member (shiny locked)
|
||||
const level = getHighestLevelPlayerPokemon(false, true).level - 2;
|
||||
const greedent = new EnemyPokemon(getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true);
|
||||
greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ];
|
||||
greedent.passive = true;
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
await catchPokemon(greedent, null, PokeballType.POKEBALL, false);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
await catchPokemon(greedent, null, PokeballType.POKEBALL, false);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
||||
function doGreedentSpriteSteal() {
|
||||
const shakeDelay = 50;
|
||||
|
@ -382,70 +401,79 @@ function doGreedentSpriteSteal() {
|
|||
globalScene.tweens.chain({
|
||||
targets: greedentSprites,
|
||||
tweens: [
|
||||
{ // Slide Greedent diagonally
|
||||
{
|
||||
// Slide Greedent diagonally
|
||||
duration: slideDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
y: "+=75",
|
||||
x: "-=65",
|
||||
scale: 1.1
|
||||
scale: 1.1,
|
||||
},
|
||||
{ // Shake
|
||||
{
|
||||
// Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Shake
|
||||
{
|
||||
// Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Shake
|
||||
{
|
||||
// Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Shake
|
||||
{
|
||||
// Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Shake
|
||||
{
|
||||
// Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Shake
|
||||
{
|
||||
// Shake
|
||||
duration: shakeDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
x: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
y: (randInt(2) > 0 ? "-=" : "+=") + 5,
|
||||
},
|
||||
{ // Slide Greedent diagonally
|
||||
{
|
||||
// Slide Greedent diagonally
|
||||
duration: slideDelay,
|
||||
ease: "Cubic.easeOut",
|
||||
y: "-=75",
|
||||
x: "+=65",
|
||||
scale: 1
|
||||
scale: 1,
|
||||
},
|
||||
{ // Bounce at the end
|
||||
{
|
||||
// Bounce at the end
|
||||
duration: 300,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
y: "-=20",
|
||||
loop: 1,
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -467,16 +495,28 @@ function doGreedentEatBerries() {
|
|||
globalScene.playSound("battle_anims/PRSFX- Bug Bite");
|
||||
}
|
||||
index++;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isEat Default false. Will "create" pile when false, and remove pile when true.
|
||||
*/
|
||||
function doBerrySpritePile(isEat: boolean = false) {
|
||||
function doBerrySpritePile(isEat = false) {
|
||||
const berryAddDelay = 150;
|
||||
let animationOrder = [ "starf", "sitrus", "lansat", "salac", "apicot", "enigma", "liechi", "ganlon", "lum", "petaya", "leppa" ];
|
||||
let animationOrder = [
|
||||
"starf",
|
||||
"sitrus",
|
||||
"lansat",
|
||||
"salac",
|
||||
"apicot",
|
||||
"enigma",
|
||||
"liechi",
|
||||
"ganlon",
|
||||
"lum",
|
||||
"petaya",
|
||||
"leppa",
|
||||
];
|
||||
if (isEat) {
|
||||
animationOrder = animationOrder.reverse();
|
||||
}
|
||||
|
@ -500,7 +540,7 @@ function doBerrySpritePile(isEat: boolean = false) {
|
|||
// Animate Petaya berry falling off the pile
|
||||
if (berry === "petaya" && sprite && tintSprite && !isEat) {
|
||||
globalScene.time.delayedCall(200, () => {
|
||||
doBerryBounce([ sprite, tintSprite ], 30, 500);
|
||||
doBerryBounce([sprite, tintSprite], 30, 500);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -515,7 +555,7 @@ function doBerryBounce(berrySprites: Phaser.GameObjects.Sprite[], yd: number, ba
|
|||
globalScene.tweens.add({
|
||||
targets: berrySprites,
|
||||
y: "+=" + bounceYOffset,
|
||||
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" },
|
||||
x: { value: "+=" + bouncePower * bouncePower * 10, ease: "Linear" },
|
||||
duration: bouncePower * baseBounceDuration,
|
||||
ease: "Cubic.easeIn",
|
||||
onComplete: () => {
|
||||
|
@ -527,13 +567,13 @@ function doBerryBounce(berrySprites: Phaser.GameObjects.Sprite[], yd: number, ba
|
|||
globalScene.tweens.add({
|
||||
targets: berrySprites,
|
||||
y: "-=" + bounceYOffset,
|
||||
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" },
|
||||
x: { value: "+=" + bouncePower * bouncePower * 10, ease: "Linear" },
|
||||
duration: bouncePower * baseBounceDuration,
|
||||
ease: "Cubic.easeOut",
|
||||
onComplete: () => doBounce()
|
||||
onComplete: () => doBounce(),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { generateModifierType, leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterExp,
|
||||
updatePlayerMoney,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
|
@ -6,7 +11,11 @@ import { globalScene } from "#app/global-scene";
|
|||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import {
|
||||
AbilityRequirement,
|
||||
CombinationPokemonRequirement,
|
||||
MoveRequirement,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
|
@ -33,153 +42,152 @@ const MONEY_MAXIMUM_MULTIPLIER = 30;
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.LIEPARD.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 0,
|
||||
y: -4,
|
||||
yShadow: -4
|
||||
},
|
||||
{
|
||||
spriteKey: "rich_kid_m",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 2,
|
||||
y: 5,
|
||||
yShadow: 5
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const pokemon = getHighestStatTotalPlayerPokemon(true, true);
|
||||
export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.LIEPARD.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 0,
|
||||
y: -4,
|
||||
yShadow: -4,
|
||||
},
|
||||
{
|
||||
spriteKey: "rich_kid_m",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 2,
|
||||
y: 5,
|
||||
yShadow: 5,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const pokemon = getHighestStatTotalPlayerPokemon(true, true);
|
||||
|
||||
const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId();
|
||||
const starterValue: number = speciesStarterCosts[baseSpecies] ?? 1;
|
||||
const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER);
|
||||
const price = globalScene.getWaveMoneyAmount(multiplier);
|
||||
const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId();
|
||||
const starterValue: number = speciesStarterCosts[baseSpecies] ?? 1;
|
||||
const multiplier = Math.max((MONEY_MAXIMUM_MULTIPLIER / 10) * starterValue, MONEY_MINIMUM_MULTIPLIER);
|
||||
const price = globalScene.getWaveMoneyAmount(multiplier);
|
||||
|
||||
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("price", price.toString());
|
||||
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("price", price.toString());
|
||||
|
||||
// Store pokemon and price
|
||||
encounter.misc = {
|
||||
pokemon: pokemon,
|
||||
price: price
|
||||
};
|
||||
// Store pokemon and price
|
||||
encounter.misc = {
|
||||
pokemon: pokemon,
|
||||
price: price,
|
||||
};
|
||||
|
||||
// If player meets the combo OR requirements for option 2, populate the token
|
||||
const opt2Req = encounter.options[1].primaryPokemonRequirements[0];
|
||||
if (opt2Req.meetsRequirement()) {
|
||||
const abilityToken = encounter.dialogueTokens["option2PrimaryAbility"];
|
||||
const moveToken = encounter.dialogueTokens["option2PrimaryMove"];
|
||||
if (abilityToken) {
|
||||
encounter.setDialogueToken("moveOrAbility", abilityToken);
|
||||
} else if (moveToken) {
|
||||
encounter.setDialogueToken("moveOrAbility", moveToken);
|
||||
}
|
||||
// If player meets the combo OR requirements for option 2, populate the token
|
||||
const opt2Req = encounter.options[1].primaryPokemonRequirements[0];
|
||||
if (opt2Req.meetsRequirement()) {
|
||||
const abilityToken = encounter.dialogueTokens["option2PrimaryAbility"];
|
||||
const moveToken = encounter.dialogueTokens["option2PrimaryMove"];
|
||||
if (abilityToken) {
|
||||
encounter.setDialogueToken("moveOrAbility", abilityToken);
|
||||
} else if (moveToken) {
|
||||
encounter.setDialogueToken("moveOrAbility", moveToken);
|
||||
}
|
||||
}
|
||||
|
||||
const shinyCharm = generateModifierType(modifierTypes.SHINY_CHARM);
|
||||
encounter.setDialogueToken("itemName", shinyCharm?.name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name"));
|
||||
encounter.setDialogueToken("liepardName", getPokemonSpecies(Species.LIEPARD).getName());
|
||||
const shinyCharm = generateModifierType(modifierTypes.SHINY_CHARM);
|
||||
encounter.setDialogueToken("itemName", shinyCharm?.name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name"));
|
||||
encounter.setDialogueToken("liepardName", getPokemonSpecies(Species.LIEPARD).getName());
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Update money and remove pokemon from party
|
||||
updatePlayerMoney(encounter.misc.price);
|
||||
globalScene.removePokemonFromPlayerParty(encounter.misc.pokemon);
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player a Shiny Charm
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM));
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
new MoveRequirement(EXTORTION_MOVES, true),
|
||||
new AbilityRequirement(EXTORTION_ABILITIES, true)
|
||||
)
|
||||
)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.tooltip_disabled`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Extort the rich kid for money
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Update money and remove pokemon from party
|
||||
updatePlayerMoney(encounter.misc.price);
|
||||
|
||||
setEncounterExp(encounter.options[1].primaryPokemon!.id, getPokemonSpecies(Species.LIEPARD).baseExp, true);
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Update money and remove pokemon from party
|
||||
updatePlayerMoney(encounter.misc.price);
|
||||
globalScene.removePokemonFromPlayerParty(encounter.misc.pokemon);
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player a Shiny Charm
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM));
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
new MoveRequirement(EXTORTION_MOVES, true),
|
||||
new AbilityRequirement(EXTORTION_ABILITIES, true),
|
||||
),
|
||||
)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.tooltip_disabled`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.3.selected`,
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Extort the rich kid for money
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Update money and remove pokemon from party
|
||||
updatePlayerMoney(encounter.misc.price);
|
||||
|
||||
setEncounterExp(encounter.options[1].primaryPokemon!.id, getPokemonSpecies(Species.LIEPARD).baseExp, true);
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import type {
|
||||
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
generateModifierTypeOption,
|
||||
|
@ -8,18 +7,12 @@ import {
|
|||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterExp,
|
||||
setEncounterRewards
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import type {
|
||||
BerryModifierType,
|
||||
ModifierTypeOption } from "#app/modifier/modifier-type";
|
||||
import {
|
||||
ModifierPoolType,
|
||||
modifierTypes,
|
||||
regenerateModifierPoolThresholds,
|
||||
} from "#app/modifier/modifier-type";
|
||||
import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
|
||||
import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
@ -30,7 +23,13 @@ import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-enco
|
|||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { applyModifierTypeToPlayerPokemon, getEncounterPokemonLevelForWave, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
getEncounterPokemonLevelForWave,
|
||||
getHighestStatPlayerPokemon,
|
||||
getSpriteKeysFromPokemon,
|
||||
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { BerryModifier } from "#app/modifier/modifier";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
|
@ -47,107 +46,150 @@ const namespace = "mysteryEncounters/berriesAbound";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3810 | GitHub Issue #3810}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const BerriesAboundEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BERRIES_ABOUND)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.BERRIES_ABOUND,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Calculate boss mon
|
||||
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
|
||||
const bossPokemon = getRandomEncounterSpecies(level, true);
|
||||
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [{
|
||||
// Calculate boss mon
|
||||
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
|
||||
const bossPokemon = getRandomEncounterSpecies(level, true);
|
||||
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [
|
||||
{
|
||||
level: level,
|
||||
species: bossPokemon.species,
|
||||
dataSource: new PokemonData(bossPokemon),
|
||||
isBoss: true
|
||||
}],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [ config ];
|
||||
|
||||
// Calculate the number of extra berries that player receives
|
||||
// 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7
|
||||
const numBerries =
|
||||
globalScene.currentBattle.waveIndex > 160 ? 7
|
||||
: globalScene.currentBattle.waveIndex > 120 ? 5
|
||||
: globalScene.currentBattle.waveIndex > 40 ? 4 : 2;
|
||||
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
|
||||
encounter.misc = { numBerries };
|
||||
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: "berries_abound_bush",
|
||||
fileRoot: "mystery-encounters",
|
||||
x: 25,
|
||||
y: -6,
|
||||
yShadow: -7,
|
||||
disableAnimation: true,
|
||||
hasShadow: true
|
||||
isBoss: true,
|
||||
},
|
||||
{
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
tint: 0.25,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
isShiny: bossPokemon.shiny,
|
||||
variant: bossPokemon.variant
|
||||
}
|
||||
];
|
||||
],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
// Get fastest party pokemon for option 2
|
||||
const fastestPokemon = getHighestStatPlayerPokemon(PERMANENT_STATS[Stat.SPD], true, false);
|
||||
encounter.misc.fastestPokemon = fastestPokemon;
|
||||
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD);
|
||||
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender());
|
||||
// Calculate the number of extra berries that player receives
|
||||
// 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7
|
||||
const numBerries =
|
||||
globalScene.currentBattle.waveIndex > 160
|
||||
? 7
|
||||
: globalScene.currentBattle.waveIndex > 120
|
||||
? 5
|
||||
: globalScene.currentBattle.waveIndex > 40
|
||||
? 4
|
||||
: 2;
|
||||
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
|
||||
encounter.misc = { numBerries };
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
spriteKey: "berries_abound_bush",
|
||||
fileRoot: "mystery-encounters",
|
||||
x: 25,
|
||||
y: -6,
|
||||
yShadow: -7,
|
||||
disableAnimation: true,
|
||||
hasShadow: true,
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
{
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
tint: 0.25,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
isShiny: bossPokemon.shiny,
|
||||
variant: bossPokemon.variant,
|
||||
},
|
||||
];
|
||||
|
||||
// Get fastest party pokemon for option 2
|
||||
const fastestPokemon = getHighestStatPlayerPokemon(PERMANENT_STATS[Stat.SPD], true, false);
|
||||
encounter.misc.fastestPokemon = fastestPokemon;
|
||||
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD);
|
||||
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender());
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const numBerries = encounter.misc.numBerries;
|
||||
|
||||
const doBerryRewards = () => {
|
||||
const berryText = i18next.t(`${namespace}:berries`);
|
||||
|
||||
globalScene.playSound("item_fanfare");
|
||||
queueEncounterMessage(
|
||||
i18next.t("battle:rewardGainCount", {
|
||||
modifierName: berryText,
|
||||
count: numBerries,
|
||||
}),
|
||||
);
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it
|
||||
for (let i = 0; i < numBerries; i++) {
|
||||
tryGiveBerry();
|
||||
}
|
||||
};
|
||||
|
||||
const shopOptions: ModifierTypeOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Generate shop berries
|
||||
const mod = generateModifierTypeOption(modifierTypes.BERRY);
|
||||
if (mod) {
|
||||
shopOptions.push(mod);
|
||||
}
|
||||
}
|
||||
|
||||
setEncounterRewards(
|
||||
{ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false },
|
||||
undefined,
|
||||
doBerryRewards,
|
||||
);
|
||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
},
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Pick race for berries
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const numBerries = encounter.misc.numBerries;
|
||||
|
||||
const doBerryRewards = () => {
|
||||
const berryText = i18next.t(`${namespace}:berries`);
|
||||
|
||||
globalScene.playSound("item_fanfare");
|
||||
queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries }));
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it
|
||||
for (let i = 0; i < numBerries; i++) {
|
||||
tryGiveBerry();
|
||||
}
|
||||
};
|
||||
const fastestPokemon: PlayerPokemon = encounter.misc.fastestPokemon;
|
||||
const enemySpeed: number = encounter.misc.enemySpeed;
|
||||
const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1);
|
||||
const numBerries: number = encounter.misc.numBerries;
|
||||
|
||||
const shopOptions: ModifierTypeOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
|
@ -158,115 +200,126 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
|||
}
|
||||
}
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
|
||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Pick race for berries
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const fastestPokemon: PlayerPokemon = encounter.misc.fastestPokemon;
|
||||
const enemySpeed: number = encounter.misc.enemySpeed;
|
||||
const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1);
|
||||
const numBerries: number = encounter.misc.numBerries;
|
||||
if (speedDiff < 1) {
|
||||
// Caught and attacked by boss, gets +1 to all stats at start of fight
|
||||
const doBerryRewards = () => {
|
||||
const berryText = i18next.t(`${namespace}:berries`);
|
||||
|
||||
const shopOptions: ModifierTypeOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Generate shop berries
|
||||
const mod = generateModifierTypeOption(modifierTypes.BERRY);
|
||||
if (mod) {
|
||||
shopOptions.push(mod);
|
||||
globalScene.playSound("item_fanfare");
|
||||
queueEncounterMessage(
|
||||
i18next.t("battle:rewardGainCount", {
|
||||
modifierName: berryText,
|
||||
count: numBerries,
|
||||
}),
|
||||
);
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it
|
||||
for (let i = 0; i < numBerries; i++) {
|
||||
tryGiveBerry();
|
||||
}
|
||||
};
|
||||
|
||||
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
|
||||
const statChangesForBattle: (
|
||||
| Stat.ATK
|
||||
| Stat.DEF
|
||||
| Stat.SPATK
|
||||
| Stat.SPDEF
|
||||
| Stat.SPD
|
||||
| Stat.ACC
|
||||
| Stat.EVA
|
||||
)[] =
|
||||
globalScene.currentBattle.waveIndex < 50
|
||||
? [Stat.DEF, Stat.SPDEF, Stat.SPD]
|
||||
: [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
|
||||
|
||||
const config = globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
|
||||
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
||||
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.2.boss_enraged`);
|
||||
globalScene.unshiftPhase(
|
||||
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1),
|
||||
);
|
||||
};
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeOptions: shopOptions,
|
||||
fillRemaining: false,
|
||||
},
|
||||
undefined,
|
||||
doBerryRewards,
|
||||
);
|
||||
await showEncounterText(`${namespace}:option.2.selected_bad`);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
return;
|
||||
}
|
||||
// Gains 1 berry for every 10% faster the player's pokemon is than the enemy, up to a max of numBerries, minimum of 2
|
||||
const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1) / 0.08), numBerries), 2);
|
||||
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
|
||||
const doFasterBerryRewards = () => {
|
||||
const berryText = i18next.t(`${namespace}:berries`);
|
||||
|
||||
globalScene.playSound("item_fanfare");
|
||||
queueEncounterMessage(
|
||||
i18next.t("battle:rewardGainCount", {
|
||||
modifierName: berryText,
|
||||
count: numBerriesGrabbed,
|
||||
}),
|
||||
);
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first)
|
||||
for (let i = 0; i < numBerriesGrabbed; i++) {
|
||||
tryGiveBerry(fastestPokemon);
|
||||
}
|
||||
};
|
||||
|
||||
if (speedDiff < 1) {
|
||||
// Caught and attacked by boss, gets +1 to all stats at start of fight
|
||||
const doBerryRewards = () => {
|
||||
const berryText = i18next.t(`${namespace}:berries`);
|
||||
|
||||
globalScene.playSound("item_fanfare");
|
||||
queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries }));
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it
|
||||
for (let i = 0; i < numBerries; i++) {
|
||||
tryGiveBerry();
|
||||
}
|
||||
};
|
||||
|
||||
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
|
||||
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
|
||||
[ Stat.DEF, Stat.SPDEF, Stat.SPD ] :
|
||||
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
|
||||
|
||||
const config = globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
|
||||
config.pokemonConfigs![0].tags = [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ];
|
||||
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.2.boss_enraged`);
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
|
||||
};
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
|
||||
await showEncounterText(`${namespace}:option.2.selected_bad`);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
return;
|
||||
} else {
|
||||
// Gains 1 berry for every 10% faster the player's pokemon is than the enemy, up to a max of numBerries, minimum of 2
|
||||
const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1) / 0.08), numBerries), 2);
|
||||
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
|
||||
const doFasterBerryRewards = () => {
|
||||
const berryText = i18next.t(`${namespace}:berries`);
|
||||
|
||||
globalScene.playSound("item_fanfare");
|
||||
queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerriesGrabbed }));
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first)
|
||||
for (let i = 0; i < numBerriesGrabbed; i++) {
|
||||
tryGiveBerry(fastestPokemon);
|
||||
}
|
||||
};
|
||||
|
||||
setEncounterExp(fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doFasterBerryRewards);
|
||||
await showEncounterText(`${namespace}:option.2.selected`);
|
||||
leaveEncounterWithoutBattle();
|
||||
}
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
setEncounterExp(fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
|
||||
setEncounterRewards(
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
guaranteedModifierTypeOptions: shopOptions,
|
||||
fillRemaining: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
undefined,
|
||||
doFasterBerryRewards,
|
||||
);
|
||||
await showEncounterText(`${namespace}:option.2.selected`);
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
|
||||
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType;
|
||||
const berry = generateModifierType(modifierTypes.BERRY, [ berryType ]) as BerryModifierType;
|
||||
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !Number.isNaN(Number(s))).length) as BerryType;
|
||||
const berry = generateModifierType(modifierTypes.BERRY, [berryType]) as BerryModifierType;
|
||||
|
||||
const party = globalScene.getPlayerParty();
|
||||
|
||||
// Will try to apply to prioritized pokemon first, then do normal application method if it fails
|
||||
if (prioritizedPokemon) {
|
||||
const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier
|
||||
&& m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
|
||||
const heldBerriesOfType = globalScene.findModifier(
|
||||
m =>
|
||||
m instanceof BerryModifier &&
|
||||
m.pokemonId === prioritizedPokemon.id &&
|
||||
(m as BerryModifier).berryType === berryType,
|
||||
true,
|
||||
) as BerryModifier;
|
||||
|
||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
|
||||
applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry);
|
||||
|
@ -276,8 +329,10 @@ function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
|
|||
|
||||
// Iterate over the party until berry was successfully given
|
||||
for (const pokemon of party) {
|
||||
const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier
|
||||
&& m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
|
||||
const heldBerriesOfType = globalScene.findModifier(
|
||||
m => m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType,
|
||||
true,
|
||||
) as BerryModifier;
|
||||
|
||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
|
||||
applyModifierTypeToPlayerPokemon(pokemon, berry);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type {
|
||||
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
generateModifierTypeOption,
|
||||
|
@ -39,20 +38,18 @@ import {
|
|||
AttackTypeBoosterHeldItemTypeRequirement,
|
||||
CombinationPokemonRequirement,
|
||||
HeldItemRequirement,
|
||||
TypeRequirement
|
||||
TypeRequirement,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import type {
|
||||
PokemonHeldItemModifier
|
||||
} from "#app/modifier/modifier";
|
||||
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import {
|
||||
AttackTypeBoosterModifier,
|
||||
BypassSpeedChanceModifier,
|
||||
ContactHeldItemTransferChanceModifier,
|
||||
GigantamaxAccessModifier,
|
||||
MegaEvolutionAccessModifier
|
||||
MegaEvolutionAccessModifier,
|
||||
} from "#app/modifier/modifier";
|
||||
import i18next from "i18next";
|
||||
import MoveInfoOverlay from "#app/ui/move-info-overlay";
|
||||
|
@ -87,7 +84,7 @@ const POOL_1_POKEMON = [
|
|||
Species.CHARJABUG,
|
||||
Species.RIBOMBEE,
|
||||
Species.SPIDOPS,
|
||||
Species.LOKIX
|
||||
Species.LOKIX,
|
||||
];
|
||||
|
||||
const POOL_2_POKEMON = [
|
||||
|
@ -116,26 +113,26 @@ const POOL_2_POKEMON = [
|
|||
Species.KLEAVOR,
|
||||
];
|
||||
|
||||
const POOL_3_POKEMON: { species: Species, formIndex?: number }[] = [
|
||||
const POOL_3_POKEMON: { species: Species; formIndex?: number }[] = [
|
||||
{
|
||||
species: Species.PINSIR,
|
||||
formIndex: 1
|
||||
formIndex: 1,
|
||||
},
|
||||
{
|
||||
species: Species.SCIZOR,
|
||||
formIndex: 1
|
||||
formIndex: 1,
|
||||
},
|
||||
{
|
||||
species: Species.HERACROSS,
|
||||
formIndex: 1
|
||||
formIndex: 1,
|
||||
},
|
||||
{
|
||||
species: Species.ORBEETLE,
|
||||
formIndex: 1
|
||||
formIndex: 1,
|
||||
},
|
||||
{
|
||||
species: Species.CENTISKORCH,
|
||||
formIndex: 1
|
||||
formIndex: 1,
|
||||
},
|
||||
{
|
||||
species: Species.DURANT,
|
||||
|
@ -148,35 +145,19 @@ const POOL_3_POKEMON: { species: Species, formIndex?: number }[] = [
|
|||
},
|
||||
];
|
||||
|
||||
const POOL_4_POKEMON = [
|
||||
Species.GENESECT,
|
||||
Species.SLITHER_WING,
|
||||
Species.BUZZWOLE,
|
||||
Species.PHEROMOSA
|
||||
];
|
||||
const POOL_4_POKEMON = [Species.GENESECT, Species.SLITHER_WING, Species.BUZZWOLE, Species.PHEROMOSA];
|
||||
|
||||
const PHYSICAL_TUTOR_MOVES = [
|
||||
Moves.MEGAHORN,
|
||||
Moves.X_SCISSOR,
|
||||
Moves.ATTACK_ORDER,
|
||||
Moves.PIN_MISSILE,
|
||||
Moves.FIRST_IMPRESSION
|
||||
Moves.FIRST_IMPRESSION,
|
||||
];
|
||||
|
||||
const SPECIAL_TUTOR_MOVES = [
|
||||
Moves.SILVER_WIND,
|
||||
Moves.BUG_BUZZ,
|
||||
Moves.SIGNAL_BEAM,
|
||||
Moves.POLLEN_PUFF
|
||||
];
|
||||
const SPECIAL_TUTOR_MOVES = [Moves.SILVER_WIND, Moves.BUG_BUZZ, Moves.SIGNAL_BEAM, Moves.POLLEN_PUFF];
|
||||
|
||||
const STATUS_TUTOR_MOVES = [
|
||||
Moves.STRING_SHOT,
|
||||
Moves.STICKY_WEB,
|
||||
Moves.SILK_TRAP,
|
||||
Moves.RAGE_POWDER,
|
||||
Moves.HEAL_ORDER
|
||||
];
|
||||
const STATUS_TUTOR_MOVES = [Moves.STRING_SHOT, Moves.STICKY_WEB, Moves.SILK_TRAP, Moves.RAGE_POWDER, Moves.HEAL_ORDER];
|
||||
|
||||
const MISC_TUTOR_MOVES = [
|
||||
Moves.BUG_BITE,
|
||||
|
@ -185,154 +166,155 @@ const MISC_TUTOR_MOVES = [
|
|||
Moves.QUIVER_DANCE,
|
||||
Moves.TAIL_GLOW,
|
||||
Moves.INFESTATION,
|
||||
Moves.U_TURN
|
||||
Moves.U_TURN,
|
||||
];
|
||||
|
||||
/**
|
||||
* Wave breakpoints that determine how strong to make the Bug-Type Superfan's team
|
||||
*/
|
||||
const WAVE_LEVEL_BREAKPOINTS = [ 30, 50, 70, 100, 120, 140, 160 ];
|
||||
const WAVE_LEVEL_BREAKPOINTS = [30, 50, 70, 100, 120, 140, 160];
|
||||
|
||||
/**
|
||||
* Bug Type Superfan encounter.
|
||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3820 | GitHub Issue #3820}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const BugTypeSuperfanEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BUG_TYPE_SUPERFAN)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
// Must have at least 1 Bug type on team, OR have a bug item somewhere on the team
|
||||
new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1),
|
||||
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1),
|
||||
new TypeRequirement(PokemonType.BUG, false, 1)
|
||||
)
|
||||
)
|
||||
.withMaxAllowedEncounters(1)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroDialogue([
|
||||
export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.BUG_TYPE_SUPERFAN,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
// Must have at least 1 Bug type on team, OR have a bug item somewhere on the team
|
||||
new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1),
|
||||
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1),
|
||||
new TypeRequirement(PokemonType.BUG, false, 1),
|
||||
),
|
||||
)
|
||||
.withMaxAllowedEncounters(1)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Calculates what trainers are available for battle in the encounter
|
||||
|
||||
// Bug type superfan trainer config
|
||||
const config = getTrainerConfigForWave(globalScene.currentBattle.waveIndex);
|
||||
const spriteKey = config.getSpriteKey();
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: config,
|
||||
female: true,
|
||||
});
|
||||
|
||||
let beedrillKeys: { spriteKey: string; fileRoot: string }, butterfreeKeys: { spriteKey: string; fileRoot: string };
|
||||
if (globalScene.currentBattle.waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) {
|
||||
beedrillKeys = getSpriteKeysFromSpecies(Species.BEEDRILL, false);
|
||||
butterfreeKeys = getSpriteKeysFromSpecies(Species.BUTTERFREE, false);
|
||||
} else {
|
||||
// Mega Beedrill/Gmax Butterfree
|
||||
beedrillKeys = getSpriteKeysFromSpecies(Species.BEEDRILL, false, 1);
|
||||
butterfreeKeys = getSpriteKeysFromSpecies(Species.BUTTERFREE, false, 1);
|
||||
}
|
||||
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
spriteKey: beedrillKeys.spriteKey,
|
||||
fileRoot: beedrillKeys.fileRoot,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
x: -30,
|
||||
tint: 0.15,
|
||||
y: -4,
|
||||
yShadow: -4,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
spriteKey: butterfreeKeys.spriteKey,
|
||||
fileRoot: butterfreeKeys.fileRoot,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
x: 30,
|
||||
tint: 0.15,
|
||||
y: -4,
|
||||
yShadow: -4,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
{
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 4,
|
||||
y: 7,
|
||||
yShadow: 7,
|
||||
},
|
||||
];
|
||||
|
||||
const requiredItems = [
|
||||
generateModifierType(modifierTypes.QUICK_CLAW),
|
||||
generateModifierType(modifierTypes.GRIP_CLAW),
|
||||
generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.BUG]),
|
||||
];
|
||||
|
||||
const requiredItemString = requiredItems.map(m => m?.name ?? "unknown").join("/");
|
||||
encounter.setDialogueToken("requiredBugItems", requiredItemString);
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Select battle the bug trainer
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Calculates what trainers are available for battle in the encounter
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
// Bug type superfan trainer config
|
||||
const config = getTrainerConfigForWave(globalScene.currentBattle.waveIndex);
|
||||
const spriteKey = config.getSpriteKey();
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: config,
|
||||
female: true,
|
||||
});
|
||||
// Init the moves available for tutor
|
||||
const moveTutorOptions: PokemonMove[] = [];
|
||||
moveTutorOptions.push(new PokemonMove(PHYSICAL_TUTOR_MOVES[randSeedInt(PHYSICAL_TUTOR_MOVES.length)]));
|
||||
moveTutorOptions.push(new PokemonMove(SPECIAL_TUTOR_MOVES[randSeedInt(SPECIAL_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)]));
|
||||
encounter.misc = {
|
||||
moveTutorOptions,
|
||||
};
|
||||
|
||||
let beedrillKeys: { spriteKey: string, fileRoot: string }, butterfreeKeys: { spriteKey: string, fileRoot: string };
|
||||
if (globalScene.currentBattle.waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) {
|
||||
beedrillKeys = getSpriteKeysFromSpecies(Species.BEEDRILL, false);
|
||||
butterfreeKeys = getSpriteKeysFromSpecies(Species.BUTTERFREE, false);
|
||||
} else {
|
||||
// Mega Beedrill/Gmax Butterfree
|
||||
beedrillKeys = getSpriteKeysFromSpecies(Species.BEEDRILL, false, 1);
|
||||
butterfreeKeys = getSpriteKeysFromSpecies(Species.BUTTERFREE, false, 1);
|
||||
}
|
||||
// Assigns callback that teaches move before continuing to rewards
|
||||
encounter.onRewards = doBugTypeMoveTutor;
|
||||
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: beedrillKeys.spriteKey,
|
||||
fileRoot: beedrillKeys.fileRoot,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
x: -30,
|
||||
tint: 0.15,
|
||||
y: -4,
|
||||
yShadow: -4
|
||||
},
|
||||
{
|
||||
spriteKey: butterfreeKeys.spriteKey,
|
||||
fileRoot: butterfreeKeys.fileRoot,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
x: 30,
|
||||
tint: 0.15,
|
||||
y: -4,
|
||||
yShadow: -4
|
||||
},
|
||||
{
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 4,
|
||||
y: 7,
|
||||
yShadow: 7
|
||||
},
|
||||
];
|
||||
|
||||
const requiredItems = [
|
||||
generateModifierType(modifierTypes.QUICK_CLAW),
|
||||
generateModifierType(modifierTypes.GRIP_CLAW),
|
||||
generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ PokemonType.BUG ]),
|
||||
];
|
||||
|
||||
const requiredItemString = requiredItems.map(m => m?.name ?? "unknown").join("/");
|
||||
encounter.setDialogueToken("requiredBugItems", requiredItemString);
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Select battle the bug trainer
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
// Init the moves available for tutor
|
||||
const moveTutorOptions: PokemonMove[] = [];
|
||||
moveTutorOptions.push(new PokemonMove(PHYSICAL_TUTOR_MOVES[randSeedInt(PHYSICAL_TUTOR_MOVES.length)]));
|
||||
moveTutorOptions.push(new PokemonMove(SPECIAL_TUTOR_MOVES[randSeedInt(SPECIAL_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)]));
|
||||
encounter.misc = {
|
||||
moveTutorOptions
|
||||
};
|
||||
|
||||
// Assigns callback that teaches move before continuing to rewards
|
||||
encounter.onRewards = doBugTypeMoveTutor;
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
await transitionMysteryEncounterIntroVisuals(true, true);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
}
|
||||
)
|
||||
.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
await transitionMysteryEncounterIntroVisuals(true, true);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
},
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new TypeRequirement(PokemonType.BUG, false, 1)) // Must have 1 Bug type on team
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Player shows off their bug types
|
||||
|
@ -340,11 +322,16 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
|||
|
||||
// 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 numBugTypesText = i18next.t(`${namespace}:numBugTypes`, { count: numBugTypes });
|
||||
const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, {
|
||||
count: numBugTypes,
|
||||
});
|
||||
encounter.setDialogueToken("numBugTypes", numBugTypesText);
|
||||
|
||||
if (numBugTypes < 2) {
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL ], fillRemaining: false });
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
|
@ -352,7 +339,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
|||
},
|
||||
];
|
||||
} else if (numBugTypes < 4) {
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL ], fillRemaining: false });
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
|
@ -360,7 +350,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
|||
},
|
||||
];
|
||||
} else if (numBugTypes < 6) {
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL ], fillRemaining: false });
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
|
@ -370,7 +363,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
|||
} else {
|
||||
// If the player has any evolution/form change items that are valid for their party,
|
||||
// spawn one of those items in addition to Dynamax Band, Mega Band, and Master Ball
|
||||
const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(modifierTypes.MASTER_BALL)! ];
|
||||
const modifierOptions: ModifierTypeOption[] = [generateModifierTypeOption(modifierTypes.MASTER_BALL)!];
|
||||
const specialOptions: ModifierTypeOption[] = [];
|
||||
|
||||
if (!globalScene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) {
|
||||
|
@ -399,7 +392,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
|||
modifierOptions.push(specialOptions[randSeedInt(specialOptions.length)]);
|
||||
}
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: modifierOptions, fillRemaining: false });
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifierOptions,
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
|
@ -412,15 +408,16 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
|||
// Player shows off their bug types
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build())
|
||||
.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
// Meets one or both of the below reqs
|
||||
new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1),
|
||||
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1)
|
||||
)
|
||||
new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1),
|
||||
new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1),
|
||||
),
|
||||
)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
|
@ -443,10 +440,13 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
|||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(item => {
|
||||
return (item instanceof BypassSpeedChanceModifier ||
|
||||
item instanceof ContactHeldItemTransferChanceModifier ||
|
||||
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)) &&
|
||||
item.isTransferable;
|
||||
return (
|
||||
(item instanceof BypassSpeedChanceModifier ||
|
||||
item instanceof ContactHeldItemTransferChanceModifier ||
|
||||
(item instanceof AttackTypeBoosterModifier &&
|
||||
(item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)) &&
|
||||
item.isTransferable
|
||||
);
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
|
@ -469,9 +469,12 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
|||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has valid item, it can be selected
|
||||
const hasValidItem = pokemon.getHeldItems().some(item => {
|
||||
return item instanceof BypassSpeedChanceModifier ||
|
||||
return (
|
||||
item instanceof BypassSpeedChanceModifier ||
|
||||
item instanceof ContactHeldItemTransferChanceModifier ||
|
||||
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG);
|
||||
(item instanceof AttackTypeBoosterModifier &&
|
||||
(item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)
|
||||
);
|
||||
});
|
||||
if (!hasValidItem) {
|
||||
return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null;
|
||||
|
@ -493,16 +496,21 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
|||
const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!;
|
||||
bugNet.type.tier = ModifierTier.ROGUE;
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: [ bugNet ], guaranteedModifierTypeFuncs: [ modifierTypes.REVIVER_SEED ], fillRemaining: false });
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [bugNet],
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.REVIVER_SEED],
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build())
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
.build(),
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
||||
function getTrainerConfigForWave(waveIndex: number) {
|
||||
// Bug type superfan trainer config
|
||||
|
@ -516,134 +524,186 @@ function getTrainerConfigForWave(waveIndex: number) {
|
|||
if (waveIndex < WAVE_LEVEL_BREAKPOINTS[0]) {
|
||||
// Use default template (2 AVG)
|
||||
config
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true));
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true));
|
||||
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[1]) {
|
||||
config
|
||||
.setPartyTemplates(new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE))
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_1_POKEMON, TrainerSlot.TRAINER, true));
|
||||
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[2]) {
|
||||
config
|
||||
.setPartyTemplates(new TrainerPartyTemplate(4, PartyMemberStrength.AVERAGE))
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_1_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true));
|
||||
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) {
|
||||
config
|
||||
.setPartyTemplates(new TrainerPartyTemplate(5, PartyMemberStrength.AVERAGE))
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_1_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true));
|
||||
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[4]) {
|
||||
config
|
||||
.setPartyTemplates(new TrainerPartyTemplate(5, PartyMemberStrength.AVERAGE))
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ pool3Mon.species ], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
.setPartyMemberFunc(
|
||||
0,
|
||||
getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}));
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(
|
||||
1,
|
||||
getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(
|
||||
4,
|
||||
getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[5]) {
|
||||
pool3Copy = randSeedShuffle(pool3Copy);
|
||||
const pool3Mon2 = pool3Copy.pop()!;
|
||||
config
|
||||
.setPartyTemplates(new TrainerPartyTemplate(5, PartyMemberStrength.AVERAGE))
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}))
|
||||
.setPartyMemberFunc(
|
||||
0,
|
||||
getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(
|
||||
1,
|
||||
getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ pool3Mon.species ], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}))
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ pool3Mon2.species ], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon2.formIndex)) {
|
||||
p.formIndex = pool3Mon2.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}));
|
||||
.setPartyMemberFunc(
|
||||
3,
|
||||
getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(
|
||||
4,
|
||||
getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon2.formIndex)) {
|
||||
p.formIndex = pool3Mon2.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else if (waveIndex < WAVE_LEVEL_BREAKPOINTS[6]) {
|
||||
config
|
||||
.setPartyTemplates(new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(4, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(1, PartyMemberStrength.STRONG)))
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ pool3Mon.species ], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
.setPartyTemplates(
|
||||
new TrainerPartyCompoundTemplate(
|
||||
new TrainerPartyTemplate(4, PartyMemberStrength.AVERAGE),
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
|
||||
),
|
||||
)
|
||||
.setPartyMemberFunc(
|
||||
0,
|
||||
getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}))
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(
|
||||
1,
|
||||
getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true, p => {
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(
|
||||
3,
|
||||
getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc(POOL_4_POKEMON, TrainerSlot.TRAINER, true));
|
||||
} else {
|
||||
pool3Copy = randSeedShuffle(pool3Copy);
|
||||
const pool3Mon2 = pool3Copy.pop()!;
|
||||
config
|
||||
.setPartyTemplates(new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(4, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(1, PartyMemberStrength.STRONG)))
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BEEDRILL ], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BUTTERFREE ], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ pool3Mon.species ], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
.setPartyTemplates(
|
||||
new TrainerPartyCompoundTemplate(
|
||||
new TrainerPartyTemplate(4, PartyMemberStrength.AVERAGE),
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
|
||||
),
|
||||
)
|
||||
.setPartyMemberFunc(
|
||||
0,
|
||||
getRandomPartyMemberFunc([Species.BEEDRILL], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ pool3Mon2.species ], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon2.formIndex)) {
|
||||
p.formIndex = pool3Mon2.formIndex;
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(
|
||||
1,
|
||||
getRandomPartyMemberFunc([Species.BUTTERFREE], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}))
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(
|
||||
2,
|
||||
getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(
|
||||
3,
|
||||
getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon2.formIndex)) {
|
||||
p.formIndex = pool3Mon2.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}),
|
||||
)
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc(POOL_4_POKEMON, TrainerSlot.TRAINER, true));
|
||||
}
|
||||
|
||||
|
@ -651,6 +711,7 @@ function getTrainerConfigForWave(waveIndex: number) {
|
|||
}
|
||||
|
||||
function doBugTypeMoveTutor(): Promise<void> {
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO explain
|
||||
return new Promise<void>(async resolve => {
|
||||
const moveOptions = globalScene.currentBattle.mysteryEncounter!.misc.moveTutorOptions;
|
||||
await showEncounterDialogue(`${namespace}:battle_won`, `${namespace}:speaker`);
|
||||
|
@ -663,7 +724,7 @@ function doBugTypeMoveTutor(): Promise<void> {
|
|||
right: true,
|
||||
x: 1,
|
||||
y: -MoveInfoOverlay.getHeight(overlayScale, true) - 1,
|
||||
width: (globalScene.game.canvas.width / 6) - 2,
|
||||
width: globalScene.game.canvas.width / 6 - 2,
|
||||
});
|
||||
globalScene.ui.add(moveInfoOverlay);
|
||||
|
||||
|
@ -688,7 +749,12 @@ function doBugTypeMoveTutor(): Promise<void> {
|
|||
moveInfoOverlay.setVisible(false);
|
||||
};
|
||||
|
||||
const result = await selectOptionThenPokemon(optionSelectItems, `${namespace}:teach_move_prompt`, undefined, onHoverOverCancel);
|
||||
const result = await selectOptionThenPokemon(
|
||||
optionSelectItems,
|
||||
`${namespace}:teach_move_prompt`,
|
||||
undefined,
|
||||
onHoverOverCancel,
|
||||
);
|
||||
// let forceExit = !!result;
|
||||
if (!result) {
|
||||
moveInfoOverlay.active = false;
|
||||
|
@ -699,7 +765,9 @@ function doBugTypeMoveTutor(): Promise<void> {
|
|||
|
||||
// Option select complete, handle if they are learning a move
|
||||
if (result && result.selectedOptionIndex < moveOptions.length) {
|
||||
globalScene.unshiftPhase(new LearnMovePhase(result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId));
|
||||
globalScene.unshiftPhase(
|
||||
new LearnMovePhase(result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId),
|
||||
);
|
||||
}
|
||||
|
||||
// Complete battle and go to rewards
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
selectPokemonForOption,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#app/data/trainer-config";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type";
|
||||
|
@ -14,7 +22,10 @@ import { Species } from "#enums/species";
|
|||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { applyAbilityOverrideToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
applyAbilityOverrideToPokemon,
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
@ -55,7 +66,7 @@ const RANDOM_ABILITY_POOL = [
|
|||
Abilities.MISTY_SURGE,
|
||||
Abilities.MAGICIAN,
|
||||
Abilities.SHEER_FORCE,
|
||||
Abilities.PRANKSTER
|
||||
Abilities.PRANKSTER,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -63,340 +74,363 @@ const RANDOM_ABILITY_POOL = [
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3807 | GitHub Issue #3807}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const ClowningAroundEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withDisallowedChallenges(Challenges.SINGLE_TYPE)
|
||||
.withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withAnimations(EncounterAnim.SMOKESCREEN)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.MR_MIME.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: -25,
|
||||
tint: 0.3,
|
||||
y: -3,
|
||||
yShadow: -3
|
||||
},
|
||||
{
|
||||
spriteKey: Species.BLACEPHALON.toString(),
|
||||
fileRoot: "pokemon/exp",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 25,
|
||||
tint: 0.3,
|
||||
y: -3,
|
||||
yShadow: -3
|
||||
},
|
||||
{
|
||||
spriteKey: "harlequin",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 0,
|
||||
y: 2,
|
||||
yShadow: 2
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.CLOWNING_AROUND,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withDisallowedChallenges(Challenges.SINGLE_TYPE)
|
||||
.withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withAnimations(EncounterAnim.SMOKESCREEN)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.MR_MIME.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: -25,
|
||||
tint: 0.3,
|
||||
y: -3,
|
||||
yShadow: -3,
|
||||
},
|
||||
{
|
||||
spriteKey: Species.BLACEPHALON.toString(),
|
||||
fileRoot: "pokemon/exp",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 25,
|
||||
tint: 0.3,
|
||||
y: -3,
|
||||
yShadow: -3,
|
||||
},
|
||||
{
|
||||
spriteKey: "harlequin",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 0,
|
||||
y: 2,
|
||||
yShadow: 2,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
const clownTrainerType = TrainerType.HARLEQUIN;
|
||||
const clownConfig = trainerConfigs[clownTrainerType].clone();
|
||||
const clownPartyTemplate = new TrainerPartyCompoundTemplate(
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER));
|
||||
clownConfig.setPartyTemplates(clownPartyTemplate);
|
||||
clownConfig.setDoubleOnly();
|
||||
// @ts-ignore
|
||||
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
|
||||
const clownTrainerType = TrainerType.HARLEQUIN;
|
||||
const clownConfig = trainerConfigs[clownTrainerType].clone();
|
||||
const clownPartyTemplate = new TrainerPartyCompoundTemplate(
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
|
||||
);
|
||||
clownConfig.setPartyTemplates(clownPartyTemplate);
|
||||
clownConfig.setDoubleOnly();
|
||||
// @ts-ignore
|
||||
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
|
||||
|
||||
// Generate random ability for Blacephalon from pool
|
||||
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
|
||||
encounter.setDialogueToken("ability", new Ability(ability, 3).name);
|
||||
encounter.misc = { ability };
|
||||
// Generate random ability for Blacephalon from pool
|
||||
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
|
||||
encounter.setDialogueToken("ability", new Ability(ability, 3).name);
|
||||
encounter.misc = { ability };
|
||||
|
||||
// Decide the random types for Blacephalon. They should not be the same.
|
||||
const firstType: number = randSeedInt(18);
|
||||
let secondType: number = randSeedInt(17);
|
||||
if ( secondType >= firstType ) {
|
||||
secondType++;
|
||||
}
|
||||
// Decide the random types for Blacephalon. They should not be the same.
|
||||
const firstType: number = randSeedInt(18);
|
||||
let secondType: number = randSeedInt(17);
|
||||
if (secondType >= firstType) {
|
||||
secondType++;
|
||||
}
|
||||
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: clownConfig,
|
||||
pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: clownConfig,
|
||||
pokemonConfigs: [
|
||||
// Overrides first 2 pokemon to be Mr. Mime and Blacephalon
|
||||
{
|
||||
species: getPokemonSpecies(Species.MR_MIME),
|
||||
isBoss: true,
|
||||
moveSet: [Moves.TEETER_DANCE, Moves.ALLY_SWITCH, Moves.DAZZLING_GLEAM, Moves.PSYCHIC],
|
||||
},
|
||||
{
|
||||
// Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
|
||||
species: getPokemonSpecies(Species.BLACEPHALON),
|
||||
customPokemonData: new CustomPokemonData({
|
||||
ability: ability,
|
||||
types: [firstType, secondType],
|
||||
}),
|
||||
isBoss: true,
|
||||
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN],
|
||||
},
|
||||
],
|
||||
doubleBattle: true,
|
||||
});
|
||||
|
||||
// Load animations/sfx for start of fight moves
|
||||
loadCustomMovesForEncounter([Moves.ROLE_PLAY, Moves.TAUNT]);
|
||||
|
||||
encounter.setDialogueToken("blacephalonName", getPokemonSpecies(Species.BLACEPHALON).getName());
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.MR_MIME),
|
||||
isBoss: true,
|
||||
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
|
||||
species: getPokemonSpecies(Species.BLACEPHALON),
|
||||
customPokemonData: new CustomPokemonData({ ability: ability, types: [ firstType, secondType ]}),
|
||||
isBoss: true,
|
||||
moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ]
|
||||
text: `${namespace}:option.1.selected`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
],
|
||||
doubleBattle: true
|
||||
});
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn battle
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
// Load animations/sfx for start of fight moves
|
||||
loadCustomMovesForEncounter([ Moves.ROLE_PLAY, Moves.TAUNT ]);
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
|
||||
encounter.setDialogueToken("blacephalonName", getPokemonSpecies(Species.BLACEPHALON).getName());
|
||||
// TODO: when Magic Room and Wonder Room are implemented, add those to start of battle
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
// Mr. Mime copies the Blacephalon's random ability
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.ENEMY_2],
|
||||
move: new PokemonMove(Moves.ROLE_PLAY),
|
||||
ignorePp: true,
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.TAUNT),
|
||||
ignorePp: true,
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [BattlerIndex.PLAYER_2],
|
||||
move: new PokemonMove(Moves.TAUNT),
|
||||
ignorePp: true,
|
||||
},
|
||||
);
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
speaker: `${namespace}:speaker`
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn battle
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.withPostOptionPhase(async (): Promise<boolean> => {
|
||||
// After the battle, offer the player the opportunity to permanently swap ability
|
||||
const abilityWasSwapped = await handleSwapAbility();
|
||||
if (abilityWasSwapped) {
|
||||
await showEncounterText(`${namespace}:option.1.ability_gained`);
|
||||
}
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
// Play animations once ability swap is complete
|
||||
// Trainer sprite that is shown at end of battle is not the same as mystery encounter intro visuals
|
||||
globalScene.tweens.add({
|
||||
targets: globalScene.currentBattle.trainer,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 250,
|
||||
});
|
||||
const background = new EncounterBattleAnim(
|
||||
EncounterAnim.SMOKESCREEN,
|
||||
globalScene.getPlayerPokemon()!,
|
||||
globalScene.getPlayerPokemon(),
|
||||
);
|
||||
background.playWithoutTargets(230, 40, 2);
|
||||
return true;
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.2.selected_2`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.2.selected_3`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Swap player's items on pokemon with the most items
|
||||
// Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items
|
||||
// So Vitamins, form change items, etc. are not included
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// TODO: when Magic Room and Wonder Room are implemented, add those to start of battle
|
||||
encounter.startOfBattleEffects.push(
|
||||
{ // Mr. Mime copies the Blacephalon's random ability
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [ BattlerIndex.ENEMY_2 ],
|
||||
move: new PokemonMove(Moves.ROLE_PLAY),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [ BattlerIndex.PLAYER ],
|
||||
move: new PokemonMove(Moves.TAUNT),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [ BattlerIndex.PLAYER_2 ],
|
||||
move: new PokemonMove(Moves.TAUNT),
|
||||
ignorePp: true
|
||||
});
|
||||
const party = globalScene.getPlayerParty();
|
||||
let mostHeldItemsPokemon = party[0];
|
||||
let count = mostHeldItemsPokemon
|
||||
.getHeldItems()
|
||||
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
|
||||
.reduce((v, m) => v + m.stackCount, 0);
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.withPostOptionPhase(async (): Promise<boolean> => {
|
||||
// After the battle, offer the player the opportunity to permanently swap ability
|
||||
const abilityWasSwapped = await handleSwapAbility();
|
||||
if (abilityWasSwapped) {
|
||||
await showEncounterText(`${namespace}:option.1.ability_gained`);
|
||||
}
|
||||
|
||||
// Play animations once ability swap is complete
|
||||
// Trainer sprite that is shown at end of battle is not the same as mystery encounter intro visuals
|
||||
globalScene.tweens.add({
|
||||
targets: globalScene.currentBattle.trainer,
|
||||
x: "+=16",
|
||||
y: "-=16",
|
||||
alpha: 0,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 250
|
||||
});
|
||||
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
|
||||
background.playWithoutTargets(230, 40, 2);
|
||||
return true;
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
speaker: `${namespace}:speaker`
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.2.selected_2`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.2.selected_3`,
|
||||
speaker: `${namespace}:speaker`
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Swap player's items on pokemon with the most items
|
||||
// Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items
|
||||
// So Vitamins, form change items, etc. are not included
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
const party = globalScene.getPlayerParty();
|
||||
let mostHeldItemsPokemon = party[0];
|
||||
let count = mostHeldItemsPokemon.getHeldItems()
|
||||
for (const pokemon of party) {
|
||||
const nextCount = pokemon
|
||||
.getHeldItems()
|
||||
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
|
||||
.reduce((v, m) => v + m.stackCount, 0);
|
||||
if (nextCount > count) {
|
||||
mostHeldItemsPokemon = pokemon;
|
||||
count = nextCount;
|
||||
}
|
||||
}
|
||||
|
||||
party.forEach(pokemon => {
|
||||
const nextCount = pokemon.getHeldItems()
|
||||
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
|
||||
.reduce((v, m) => v + m.stackCount, 0);
|
||||
if (nextCount > count) {
|
||||
mostHeldItemsPokemon = pokemon;
|
||||
count = nextCount;
|
||||
}
|
||||
});
|
||||
encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender());
|
||||
|
||||
encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender());
|
||||
const items = mostHeldItemsPokemon.getHeldItems();
|
||||
|
||||
const items = mostHeldItemsPokemon.getHeldItems();
|
||||
// Shuffles Berries (if they have any)
|
||||
let numBerries = 0;
|
||||
for (const m of items.filter(m => m instanceof BerryModifier)) {
|
||||
numBerries += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
}
|
||||
|
||||
// Shuffles Berries (if they have any)
|
||||
let numBerries = 0;
|
||||
items.filter(m => m instanceof BerryModifier)
|
||||
.forEach(m => {
|
||||
numBerries += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
});
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries");
|
||||
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries");
|
||||
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
|
||||
// For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier
|
||||
// And Golden Eggs as Rogue tier
|
||||
let numUltra = 0;
|
||||
let numRogue = 0;
|
||||
|
||||
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
|
||||
// For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier
|
||||
// And Golden Eggs as Rogue tier
|
||||
let numUltra = 0;
|
||||
let numRogue = 0;
|
||||
items.filter(m => m.isTransferable && !(m instanceof BerryModifier))
|
||||
.forEach(m => {
|
||||
const type = m.type.withTierFromPool(ModifierPoolType.PLAYER, party);
|
||||
const tier = type.tier ?? ModifierTier.ULTRA;
|
||||
if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
|
||||
numRogue += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
} else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) {
|
||||
numUltra += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
}
|
||||
});
|
||||
for (const m of items.filter(m => m.isTransferable && !(m instanceof BerryModifier))) {
|
||||
const type = m.type.withTierFromPool(ModifierPoolType.PLAYER, party);
|
||||
const tier = type.tier ?? ModifierTier.ULTRA;
|
||||
if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
|
||||
numRogue += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
} else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) {
|
||||
numUltra += m.stackCount;
|
||||
globalScene.removeModifier(m);
|
||||
}
|
||||
}
|
||||
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.withPostOptionPhase(async () => {
|
||||
// Play animations
|
||||
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
|
||||
background.playWithoutTargets(230, 40, 2);
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 200);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
speaker: `${namespace}:speaker`
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.3.selected_2`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.3.selected_3`,
|
||||
speaker: `${namespace}:speaker`
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Randomize the second type of all player's pokemon
|
||||
// If the pokemon does not normally have a second type, it will gain 1
|
||||
for (const pokemon of globalScene.getPlayerParty()) {
|
||||
const originalTypes = pokemon.getTypes(false, false, true);
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
|
||||
generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.withPostOptionPhase(async () => {
|
||||
// Play animations
|
||||
const background = new EncounterBattleAnim(
|
||||
EncounterAnim.SMOKESCREEN,
|
||||
globalScene.getPlayerPokemon()!,
|
||||
globalScene.getPlayerPokemon(),
|
||||
);
|
||||
background.playWithoutTargets(230, 40, 2);
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 200);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.3.selected_2`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.3.selected_3`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Randomize the second type of all player's pokemon
|
||||
// If the pokemon does not normally have a second type, it will gain 1
|
||||
for (const pokemon of globalScene.getPlayerParty()) {
|
||||
const originalTypes = pokemon.getTypes(false, false, true);
|
||||
|
||||
// If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type
|
||||
// Makes the "randomness" of the shuffle slightly less punishing
|
||||
let priorityTypes = pokemon.moveset
|
||||
.filter(move => move && !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS)
|
||||
.map(move => move!.getMove().type);
|
||||
if (priorityTypes?.length > 0) {
|
||||
priorityTypes = [ ...new Set(priorityTypes) ].sort();
|
||||
priorityTypes = randSeedShuffle(priorityTypes);
|
||||
}
|
||||
// If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type
|
||||
// Makes the "randomness" of the shuffle slightly less punishing
|
||||
let priorityTypes = pokemon.moveset
|
||||
.filter(
|
||||
move =>
|
||||
move && !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS,
|
||||
)
|
||||
.map(move => move!.getMove().type);
|
||||
if (priorityTypes?.length > 0) {
|
||||
priorityTypes = [...new Set(priorityTypes)].sort();
|
||||
priorityTypes = randSeedShuffle(priorityTypes);
|
||||
}
|
||||
|
||||
const newTypes = [ PokemonType.UNKNOWN ];
|
||||
let secondType: PokemonType | null = null;
|
||||
while (secondType === null || secondType === newTypes[0] || originalTypes.includes(secondType)) {
|
||||
if (priorityTypes.length > 0) {
|
||||
secondType = priorityTypes.pop() ?? null;
|
||||
} else {
|
||||
secondType = randSeedInt(18) as PokemonType;
|
||||
}
|
||||
}
|
||||
newTypes.push(secondType);
|
||||
|
||||
// Apply the type changes (to both base and fusion, if pokemon is fused)
|
||||
if (!pokemon.customPokemonData) {
|
||||
pokemon.customPokemonData = new CustomPokemonData();
|
||||
}
|
||||
pokemon.customPokemonData.types = newTypes;
|
||||
if (pokemon.isFusion()) {
|
||||
if (!pokemon.fusionCustomPokemonData) {
|
||||
pokemon.fusionCustomPokemonData = new CustomPokemonData();
|
||||
}
|
||||
pokemon.fusionCustomPokemonData.types = newTypes;
|
||||
const newTypes = [PokemonType.UNKNOWN];
|
||||
let secondType: PokemonType | null = null;
|
||||
while (secondType === null || secondType === newTypes[0] || originalTypes.includes(secondType)) {
|
||||
if (priorityTypes.length > 0) {
|
||||
secondType = priorityTypes.pop() ?? null;
|
||||
} else {
|
||||
secondType = randSeedInt(18) as PokemonType;
|
||||
}
|
||||
}
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.withPostOptionPhase(async () => {
|
||||
// Play animations
|
||||
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
|
||||
background.playWithoutTargets(230, 40, 2);
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 200);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
newTypes.push(secondType);
|
||||
|
||||
// Apply the type changes (to both base and fusion, if pokemon is fused)
|
||||
if (!pokemon.customPokemonData) {
|
||||
pokemon.customPokemonData = new CustomPokemonData();
|
||||
}
|
||||
pokemon.customPokemonData.types = newTypes;
|
||||
if (pokemon.isFusion()) {
|
||||
if (!pokemon.fusionCustomPokemonData) {
|
||||
pokemon.fusionCustomPokemonData = new CustomPokemonData();
|
||||
}
|
||||
pokemon.fusionCustomPokemonData.types = newTypes;
|
||||
}
|
||||
}
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.withPostOptionPhase(async () => {
|
||||
// Play animations
|
||||
const background = new EncounterBattleAnim(
|
||||
EncounterAnim.SMOKESCREEN,
|
||||
globalScene.getPlayerPokemon()!,
|
||||
globalScene.getPlayerPokemon(),
|
||||
);
|
||||
background.playWithoutTargets(230, 40, 2);
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 200);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
||||
async function handleSwapAbility() {
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
|
||||
return new Promise<boolean>(async resolve => {
|
||||
await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`);
|
||||
await showEncounterText(`${namespace}:option.1.apply_ability_message`);
|
||||
|
@ -415,21 +449,21 @@ function displayYesNoOptions(resolve) {
|
|||
handler: () => {
|
||||
onYesAbilitySwap(resolve);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18next.t("menu:no"),
|
||||
handler: () => {
|
||||
resolve(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const config: OptionSelectConfig = {
|
||||
options: fullOptions,
|
||||
maxOptions: 7,
|
||||
yOffset: 0
|
||||
yOffset: 0,
|
||||
};
|
||||
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
|
||||
}
|
||||
|
@ -458,36 +492,36 @@ function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: Mod
|
|||
// Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon
|
||||
// This is to prevent "over-generating" a random item of a certain type during item swaps
|
||||
const ultraPool = [
|
||||
[ modifierTypes.REVIVER_SEED, 1 ],
|
||||
[ modifierTypes.GOLDEN_PUNCH, 5 ],
|
||||
[ modifierTypes.ATTACK_TYPE_BOOSTER, 99 ],
|
||||
[ modifierTypes.QUICK_CLAW, 3 ],
|
||||
[ modifierTypes.WIDE_LENS, 3 ]
|
||||
[modifierTypes.REVIVER_SEED, 1],
|
||||
[modifierTypes.GOLDEN_PUNCH, 5],
|
||||
[modifierTypes.ATTACK_TYPE_BOOSTER, 99],
|
||||
[modifierTypes.QUICK_CLAW, 3],
|
||||
[modifierTypes.WIDE_LENS, 3],
|
||||
];
|
||||
|
||||
const roguePool = [
|
||||
[ modifierTypes.LEFTOVERS, 4 ],
|
||||
[ modifierTypes.SHELL_BELL, 4 ],
|
||||
[ modifierTypes.SOUL_DEW, 10 ],
|
||||
[ modifierTypes.SCOPE_LENS, 1 ],
|
||||
[ modifierTypes.BATON, 1 ],
|
||||
[ modifierTypes.FOCUS_BAND, 5 ],
|
||||
[ modifierTypes.KINGS_ROCK, 3 ],
|
||||
[ modifierTypes.GRIP_CLAW, 5 ]
|
||||
[modifierTypes.LEFTOVERS, 4],
|
||||
[modifierTypes.SHELL_BELL, 4],
|
||||
[modifierTypes.SOUL_DEW, 10],
|
||||
[modifierTypes.SCOPE_LENS, 1],
|
||||
[modifierTypes.BATON, 1],
|
||||
[modifierTypes.FOCUS_BAND, 5],
|
||||
[modifierTypes.KINGS_ROCK, 3],
|
||||
[modifierTypes.GRIP_CLAW, 5],
|
||||
];
|
||||
|
||||
const berryPool = [
|
||||
[ BerryType.APICOT, 3 ],
|
||||
[ BerryType.ENIGMA, 2 ],
|
||||
[ BerryType.GANLON, 3 ],
|
||||
[ BerryType.LANSAT, 3 ],
|
||||
[ BerryType.LEPPA, 2 ],
|
||||
[ BerryType.LIECHI, 3 ],
|
||||
[ BerryType.LUM, 2 ],
|
||||
[ BerryType.PETAYA, 3 ],
|
||||
[ BerryType.SALAC, 2 ],
|
||||
[ BerryType.SITRUS, 2 ],
|
||||
[ BerryType.STARF, 3 ]
|
||||
[BerryType.APICOT, 3],
|
||||
[BerryType.ENIGMA, 2],
|
||||
[BerryType.GANLON, 3],
|
||||
[BerryType.LANSAT, 3],
|
||||
[BerryType.LEPPA, 2],
|
||||
[BerryType.LIECHI, 3],
|
||||
[BerryType.LUM, 2],
|
||||
[BerryType.PETAYA, 3],
|
||||
[BerryType.SALAC, 2],
|
||||
[BerryType.SITRUS, 2],
|
||||
[BerryType.STARF, 3],
|
||||
];
|
||||
|
||||
let pool: any[];
|
||||
|
@ -506,7 +540,7 @@ function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: Mod
|
|||
const newItemType = pool[randIndex];
|
||||
let newMod: PokemonHeldItemModifierType;
|
||||
if (tier === "Berries") {
|
||||
newMod = generateModifierType(modifierTypes.BERRY, [ newItemType[0] ]) as PokemonHeldItemModifierType;
|
||||
newMod = generateModifierType(modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType;
|
||||
} else {
|
||||
newMod = generateModifierType(newItemType[0]) as PokemonHeldItemModifierType;
|
||||
}
|
||||
|
|
|
@ -8,8 +8,17 @@ import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-
|
|||
import { DANCING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { catchPokemon, getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
catchPokemon,
|
||||
getEncounterPokemonLevelForWave,
|
||||
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
|
@ -44,7 +53,7 @@ const BAILE_STYLE_BIOMES = [
|
|||
Biome.WASTELAND,
|
||||
Biome.MOUNTAIN,
|
||||
Biome.BADLANDS,
|
||||
Biome.DESERT
|
||||
Biome.DESERT,
|
||||
];
|
||||
|
||||
// Electric form
|
||||
|
@ -55,7 +64,7 @@ const POM_POM_STYLE_BIOMES = [
|
|||
Biome.LABORATORY,
|
||||
Biome.SLUM,
|
||||
Biome.METROPOLIS,
|
||||
Biome.DOJO
|
||||
Biome.DOJO,
|
||||
];
|
||||
|
||||
// Psychic form
|
||||
|
@ -66,7 +75,7 @@ const PAU_STYLE_BIOMES = [
|
|||
Biome.PLAINS,
|
||||
Biome.GRASS,
|
||||
Biome.TALL_GRASS,
|
||||
Biome.FOREST
|
||||
Biome.FOREST,
|
||||
];
|
||||
|
||||
// Ghost form
|
||||
|
@ -77,7 +86,7 @@ const SENSU_STYLE_BIOMES = [
|
|||
Biome.ABYSS,
|
||||
Biome.GRAVEYARD,
|
||||
Biome.LAKE,
|
||||
Biome.TEMPLE
|
||||
Biome.TEMPLE,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -85,239 +94,259 @@ const SENSU_STYLE_BIOMES = [
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3823 | GitHub Issue #3823}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const DancingLessonsEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DANCING_LESSONS)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([]) // Uses a real Pokemon sprite instead of ME Intro Visuals
|
||||
.withAnimations(EncounterAnim.DANCE)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withCatchAllowed(true)
|
||||
.withFleeAllowed(false)
|
||||
.withOnVisualsStart(() => {
|
||||
const oricorio = globalScene.getEnemyPokemon()!;
|
||||
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, globalScene.getPlayerPokemon()!);
|
||||
danceAnim.play(false, () => {
|
||||
if (oricorio.shiny) {
|
||||
oricorio.sparkle();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.DANCING_LESSONS,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([]) // Uses a real Pokemon sprite instead of ME Intro Visuals
|
||||
.withAnimations(EncounterAnim.DANCE)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withCatchAllowed(true)
|
||||
.withFleeAllowed(false)
|
||||
.withOnVisualsStart(() => {
|
||||
const oricorio = globalScene.getEnemyPokemon()!;
|
||||
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, globalScene.getPlayerPokemon()!);
|
||||
danceAnim.play(false, () => {
|
||||
if (oricorio.shiny) {
|
||||
oricorio.sparkle();
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
const species = getPokemonSpecies(Species.ORICORIO);
|
||||
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
|
||||
const enemyPokemon = new EnemyPokemon(species, level, TrainerSlot.NONE, false);
|
||||
if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) {
|
||||
if (enemyPokemon.moveset.length < 4) {
|
||||
enemyPokemon.moveset.push(new PokemonMove(Moves.REVELATION_DANCE));
|
||||
} else {
|
||||
enemyPokemon.moveset[0] = new PokemonMove(Moves.REVELATION_DANCE);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the form index based on the biome
|
||||
// Defaults to Baile style if somehow nothing matches
|
||||
const currentBiome = globalScene.arena.biomeType;
|
||||
if (BAILE_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 0;
|
||||
} else if (POM_POM_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 1;
|
||||
} else if (PAU_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 2;
|
||||
} else if (SENSU_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 3;
|
||||
const species = getPokemonSpecies(Species.ORICORIO);
|
||||
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
|
||||
const enemyPokemon = new EnemyPokemon(species, level, TrainerSlot.NONE, false);
|
||||
if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) {
|
||||
if (enemyPokemon.moveset.length < 4) {
|
||||
enemyPokemon.moveset.push(new PokemonMove(Moves.REVELATION_DANCE));
|
||||
} else {
|
||||
enemyPokemon.formIndex = 0;
|
||||
enemyPokemon.moveset[0] = new PokemonMove(Moves.REVELATION_DANCE);
|
||||
}
|
||||
}
|
||||
|
||||
const oricorioData = new PokemonData(enemyPokemon);
|
||||
const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
|
||||
// Set the form index based on the biome
|
||||
// Defaults to Baile style if somehow nothing matches
|
||||
const currentBiome = globalScene.arena.biomeType;
|
||||
if (BAILE_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 0;
|
||||
} else if (POM_POM_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 1;
|
||||
} else if (PAU_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 2;
|
||||
} else if (SENSU_STYLE_BIOMES.includes(currentBiome)) {
|
||||
enemyPokemon.formIndex = 3;
|
||||
} else {
|
||||
enemyPokemon.formIndex = 0;
|
||||
}
|
||||
|
||||
// Adds a real Pokemon sprite to the field (required for the animation)
|
||||
globalScene.getEnemyParty().forEach(enemyPokemon => {
|
||||
enemyPokemon.leaveField(true, true, true);
|
||||
});
|
||||
globalScene.currentBattle.enemyParty = [ oricorio ];
|
||||
globalScene.field.add(oricorio);
|
||||
// Spawns on offscreen field
|
||||
oricorio.x -= 300;
|
||||
encounter.loadAssets.push(oricorio.loadAssets());
|
||||
const oricorioData = new PokemonData(enemyPokemon);
|
||||
const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
|
||||
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [{
|
||||
// Adds a real Pokemon sprite to the field (required for the animation)
|
||||
for (const enemyPokemon of globalScene.getEnemyParty()) {
|
||||
enemyPokemon.leaveField(true, true, true);
|
||||
}
|
||||
globalScene.currentBattle.enemyParty = [oricorio];
|
||||
globalScene.field.add(oricorio);
|
||||
// Spawns on offscreen field
|
||||
oricorio.x -= 300;
|
||||
encounter.loadAssets.push(oricorio.loadAssets());
|
||||
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: species,
|
||||
dataSource: oricorioData,
|
||||
isBoss: true,
|
||||
// Gets +1 to all stats except SPD on battle start
|
||||
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF ], 1));
|
||||
globalScene.unshiftPhase(
|
||||
new StatStageChangePhase(
|
||||
pokemon.getBattlerIndex(),
|
||||
true,
|
||||
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF],
|
||||
1,
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
encounter.misc = {
|
||||
oricorioData,
|
||||
};
|
||||
|
||||
encounter.setDialogueToken("oricorioName", getPokemonSpecies(Species.ORICORIO).getName());
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
encounter.startOfBattleEffects.push({
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.REVELATION_DANCE),
|
||||
ignorePp: true,
|
||||
});
|
||||
|
||||
await hideOricorioPokemon();
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.BATON],
|
||||
fillRemaining: true,
|
||||
});
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Learn its Dance
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||
globalScene.unshiftPhase(
|
||||
new LearnMovePhase(globalScene.getPlayerParty().indexOf(pokemon), Moves.REVELATION_DANCE),
|
||||
);
|
||||
|
||||
// Play animation again to "learn" the dance
|
||||
const danceAnim = new EncounterBattleAnim(
|
||||
EncounterAnim.DANCE,
|
||||
globalScene.getEnemyPokemon()!,
|
||||
globalScene.getPlayerPokemon(),
|
||||
);
|
||||
danceAnim.play();
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Learn its Dance
|
||||
await hideOricorioPokemon();
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Open menu for selecting pokemon with a Dancing move
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for nature selection
|
||||
return pokemon.moveset
|
||||
.filter(move => move && DANCING_MOVES.includes(move.getMove().id))
|
||||
.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and second option selected
|
||||
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("selectedMove", move.getName());
|
||||
encounter.misc.selectedMove = move;
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only challenge legal/unfainted Pokemon that have a Dancing move can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return (
|
||||
i18next.t("partyUiHandler:cantBeUsed", {
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
}) ?? null
|
||||
);
|
||||
}
|
||||
}],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [ config ];
|
||||
encounter.misc = {
|
||||
oricorioData
|
||||
};
|
||||
|
||||
encounter.setDialogueToken("oricorioName", getPokemonSpecies(Species.ORICORIO).getName());
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
encounter.startOfBattleEffects.push({
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [ BattlerIndex.PLAYER ],
|
||||
move: new PokemonMove(Moves.REVELATION_DANCE),
|
||||
ignorePp: true
|
||||
});
|
||||
|
||||
await hideOricorioPokemon();
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.BATON ], fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Learn its Dance
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||
globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(pokemon), Moves.REVELATION_DANCE));
|
||||
|
||||
// Play animation again to "learn" the dance
|
||||
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, globalScene.getEnemyPokemon()!, globalScene.getPlayerPokemon());
|
||||
danceAnim.play();
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Learn its Dance
|
||||
await hideOricorioPokemon();
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Open menu for selecting pokemon with a Dancing move
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for nature selection
|
||||
return pokemon.moveset
|
||||
.filter(move => move && DANCING_MOVES.includes(move.getMove().id))
|
||||
.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and second option selected
|
||||
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("selectedMove", move.getName());
|
||||
encounter.misc.selectedMove = move;
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only challenge legal/unfainted Pokemon that have a Dancing move can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
|
||||
}
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Show the Oricorio a dance, and recruit it
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const oricorio = encounter.misc.oricorioData.toPokemon();
|
||||
oricorio.passive = true;
|
||||
|
||||
// Ensure the Oricorio's moveset gains the Dance move the player used
|
||||
const move = encounter.misc.selectedMove?.getMove().id;
|
||||
if (!oricorio.moveset.some(m => m.getMove().id === move)) {
|
||||
if (oricorio.moveset.length < 4) {
|
||||
oricorio.moveset.push(new PokemonMove(move));
|
||||
} else {
|
||||
oricorio.moveset[3] = new PokemonMove(move);
|
||||
}
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
await hideOricorioPokemon();
|
||||
await catchPokemon(oricorio, null, PokeballType.POKEBALL, false);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Show the Oricorio a dance, and recruit it
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const oricorio = encounter.misc.oricorioData.toPokemon();
|
||||
oricorio.passive = true;
|
||||
|
||||
// Ensure the Oricorio's moveset gains the Dance move the player used
|
||||
const move = encounter.misc.selectedMove?.getMove().id;
|
||||
if (!oricorio.moveset.some(m => m.getMove().id === move)) {
|
||||
if (oricorio.moveset.length < 4) {
|
||||
oricorio.moveset.push(new PokemonMove(move));
|
||||
} else {
|
||||
oricorio.moveset[3] = new PokemonMove(move);
|
||||
}
|
||||
}
|
||||
|
||||
await hideOricorioPokemon();
|
||||
await catchPokemon(oricorio, null, PokeballType.POKEBALL, false);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
||||
function hideOricorioPokemon() {
|
||||
return new Promise<void>(resolve => {
|
||||
|
@ -332,7 +361,7 @@ function hideOricorioPokemon() {
|
|||
onComplete: () => {
|
||||
globalScene.field.remove(oricorioSprite, true);
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,8 +9,11 @@ import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounte
|
|||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
|
||||
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
|
||||
import { getRandomPlayerPokemon, getRandomSpeciesByStarterCost } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle } from "../utils/encounter-phase-utils";
|
||||
import {
|
||||
getRandomPlayerPokemon,
|
||||
getRandomSpeciesByStarterCost,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||
|
@ -93,131 +96,132 @@ const excludedBosses = [
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3806 | GitHub Issue #3806}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const DarkDealEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DARK_DEAL)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "dark_deal_scientist",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "dark_deal_porygon",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
|
||||
.withCatchAllowed(true)
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.1.selected_dialogue`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.1.selected_message`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
|
||||
// Will never return last battle able mon and instead pick fainted/unable to battle
|
||||
const removedPokemon = getRandomPlayerPokemon(true, false, true);
|
||||
|
||||
// Get all the pokemon's held items
|
||||
const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
globalScene.removePokemonFromPlayerParty(removedPokemon);
|
||||
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
encounter.setDialogueToken("pokeName", removedPokemon.getNameToRender());
|
||||
|
||||
// Store removed pokemon types
|
||||
encounter.misc = {
|
||||
removedTypes: removedPokemon.getTypes(),
|
||||
modifiers
|
||||
};
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player 5 Rogue Balls
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.ROGUE_BALL));
|
||||
|
||||
// Start encounter with random legendary (7-10 starter strength) that has level additive
|
||||
// If this is a mono-type challenge, always ensure the required type is filtered for
|
||||
let bossTypes: PokemonType[] = encounter.misc.removedTypes;
|
||||
const singleTypeChallenges = globalScene.gameMode.challenges.filter(c => c.value && c.id === Challenges.SINGLE_TYPE);
|
||||
if (globalScene.gameMode.isChallenge && singleTypeChallenges.length > 0) {
|
||||
bossTypes = singleTypeChallenges.map(c => (c.value - 1) as PokemonType);
|
||||
}
|
||||
|
||||
const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers;
|
||||
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
|
||||
const roll = randSeedInt(100);
|
||||
const starterTier: number | [number, number] =
|
||||
roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [ 9, 10 ];
|
||||
const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterCost(starterTier, excludedBosses, bossTypes));
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
modifierConfigs: bossModifiers.map(m => {
|
||||
return {
|
||||
modifier: m,
|
||||
stackCount: m.getStackCount(),
|
||||
};
|
||||
})
|
||||
};
|
||||
if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) {
|
||||
pokemonConfig.formIndex = 0;
|
||||
}
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [ pokemonConfig ],
|
||||
};
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.DARK_DEAL,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "dark_deal_scientist",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "dark_deal_porygon",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
|
||||
.withCatchAllowed(true)
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.2.selected`,
|
||||
text: `${namespace}:option.1.selected_dialogue`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:option.1.selected_message`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`
|
||||
}
|
||||
])
|
||||
.build();
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
|
||||
// Will never return last battle able mon and instead pick fainted/unable to battle
|
||||
const removedPokemon = getRandomPlayerPokemon(true, false, true);
|
||||
|
||||
// Get all the pokemon's held items
|
||||
const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
globalScene.removePokemonFromPlayerParty(removedPokemon);
|
||||
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
encounter.setDialogueToken("pokeName", removedPokemon.getNameToRender());
|
||||
|
||||
// Store removed pokemon types
|
||||
encounter.misc = {
|
||||
removedTypes: removedPokemon.getTypes(),
|
||||
modifiers,
|
||||
};
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player 5 Rogue Balls
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.ROGUE_BALL));
|
||||
|
||||
// Start encounter with random legendary (7-10 starter strength) that has level additive
|
||||
// If this is a mono-type challenge, always ensure the required type is filtered for
|
||||
let bossTypes: PokemonType[] = encounter.misc.removedTypes;
|
||||
const singleTypeChallenges = globalScene.gameMode.challenges.filter(
|
||||
c => c.value && c.id === Challenges.SINGLE_TYPE,
|
||||
);
|
||||
if (globalScene.gameMode.isChallenge && singleTypeChallenges.length > 0) {
|
||||
bossTypes = singleTypeChallenges.map(c => (c.value - 1) as PokemonType);
|
||||
}
|
||||
|
||||
const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers;
|
||||
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
|
||||
const roll = randSeedInt(100);
|
||||
const starterTier: number | [number, number] = roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [9, 10];
|
||||
const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterCost(starterTier, excludedBosses, bossTypes));
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
modifierConfigs: bossModifiers.map(m => {
|
||||
return {
|
||||
modifier: m,
|
||||
stackCount: m.getStackCount(),
|
||||
};
|
||||
}),
|
||||
};
|
||||
if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) {
|
||||
pokemonConfig.formIndex = 0;
|
||||
}
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [pokemonConfig],
|
||||
};
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
|
|
@ -2,16 +2,31 @@ import { globalScene } from "#app/global-scene";
|
|||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import {
|
||||
CombinationPokemonRequirement,
|
||||
HeldItemRequirement,
|
||||
MoneyRequirement,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
updatePlayerMoney,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
|
||||
import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PreserveBerryModifier } from "#app/modifier/modifier";
|
||||
import {
|
||||
BerryModifier,
|
||||
HealingBoosterModifier,
|
||||
LevelIncrementBoosterModifier,
|
||||
MoneyMultiplierModifier,
|
||||
PreserveBerryModifier,
|
||||
} from "#app/modifier/modifier";
|
||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||
|
@ -27,7 +42,7 @@ import { Species } from "#enums/species";
|
|||
const namespace = "mysteryEncounters/delibirdy";
|
||||
|
||||
/** Berries only */
|
||||
const OPTION_2_ALLOWED_MODIFIERS = [ "BerryModifier", "PokemonInstantReviveModifier" ];
|
||||
const OPTION_2_ALLOWED_MODIFIERS = ["BerryModifier", "PokemonInstantReviveModifier"];
|
||||
|
||||
/** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */
|
||||
const OPTION_3_DISALLOWED_MODIFIERS = [
|
||||
|
@ -35,7 +50,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [
|
|||
"PokemonInstantReviveModifier",
|
||||
"TerastallizeModifier",
|
||||
"PokemonBaseStatModifier",
|
||||
"PokemonBaseStatTotalModifier"
|
||||
"PokemonBaseStatTotalModifier",
|
||||
];
|
||||
|
||||
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
|
||||
|
@ -43,11 +58,11 @@ const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
|
|||
const doEventReward = () => {
|
||||
const event_buff = globalScene.eventManager.getDelibirdyBuff();
|
||||
if (event_buff.length > 0) {
|
||||
const candidates = event_buff.filter((c => {
|
||||
const candidates = event_buff.filter(c => {
|
||||
const mtype = generateModifierType(modifierTypes[c]);
|
||||
const existingCharm = globalScene.findModifier(m => m.type.id === mtype?.id);
|
||||
return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount());
|
||||
}));
|
||||
});
|
||||
if (candidates.length > 0) {
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes[randSeedItem(candidates)]));
|
||||
} else {
|
||||
|
@ -62,282 +77,308 @@ const doEventReward = () => {
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3804 | GitHub Issue #3804}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const DelibirdyEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
// Must also have either option 2 or 3 available to spawn
|
||||
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),
|
||||
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true)
|
||||
)
|
||||
)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.DELIBIRD,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
startFrame: 38,
|
||||
scale: 0.94
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.DELIBIRD,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
scale: 1.06
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.DELIBIRD,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
startFrame: 65,
|
||||
x: 1,
|
||||
y: 5,
|
||||
yShadow: 5
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
}
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
encounter.setDialogueToken("delibirdName", getPokemonSpecies(Species.DELIBIRD).getName());
|
||||
export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.DELIBIRDY,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
// Must also have either option 2 or 3 available to spawn
|
||||
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),
|
||||
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true),
|
||||
),
|
||||
)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.DELIBIRD,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
startFrame: 38,
|
||||
scale: 0.94,
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.DELIBIRD,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
scale: 1.06,
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.DELIBIRD,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
startFrame: 65,
|
||||
x: 1,
|
||||
y: 5,
|
||||
yShadow: 5,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
encounter.setDialogueToken("delibirdName", getPokemonSpecies(Species.DELIBIRD).getName());
|
||||
|
||||
globalScene.loadBgm("mystery_encounter_delibirdy", "mystery_encounter_delibirdy.mp3");
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
globalScene.fadeAndSwitchBgm("mystery_encounter_delibirdy");
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER) // Must have money to spawn
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false);
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player an Amulet Coin
|
||||
// Check if the player has max stacks of that item already
|
||||
const existing = globalScene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier;
|
||||
globalScene.loadBgm("mystery_encounter_delibirdy", "mystery_encounter_delibirdy.mp3");
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
globalScene.fadeAndSwitchBgm("mystery_encounter_delibirdy");
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER) // Must have money to spawn
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false);
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give the player an Amulet Coin
|
||||
// Check if the player has max stacks of that item already
|
||||
const existing = globalScene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", { modifierName: shellBell.name }),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
} else {
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.AMULET_COIN));
|
||||
doEventReward();
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.2.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(it => {
|
||||
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has valid item, it can be selected
|
||||
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier: BerryModifier | PokemonInstantReviveModifier = encounter.misc.chosenModifier;
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed
|
||||
if (modifier instanceof BerryModifier) {
|
||||
// Check if the player has max stacks of that Candy Jar already
|
||||
const existing = globalScene.findModifier(
|
||||
m => m instanceof LevelIncrementBoosterModifier,
|
||||
) as LevelIncrementBoosterModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", {
|
||||
modifierName: shellBell.name,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
} else {
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.AMULET_COIN));
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.CANDY_JAR));
|
||||
doEventReward();
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.2.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
||||
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has valid item, it can be selected
|
||||
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier: BerryModifier | PokemonInstantReviveModifier = encounter.misc.chosenModifier;
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed
|
||||
if (modifier instanceof BerryModifier) {
|
||||
// Check if the player has max stacks of that Candy Jar already
|
||||
const existing = globalScene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
|
||||
doEventReward();
|
||||
} else {
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.CANDY_JAR));
|
||||
doEventReward();
|
||||
}
|
||||
} else {
|
||||
// Check if the player has max stacks of that Berry Pouch already
|
||||
const existing = globalScene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
|
||||
doEventReward();
|
||||
} else {
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.BERRY_POUCH));
|
||||
doEventReward();
|
||||
}
|
||||
}
|
||||
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
||||
return !OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has valid item, it can be selected
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Check if the player has max stacks of Healing Charm already
|
||||
const existing = globalScene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier;
|
||||
} else {
|
||||
// Check if the player has max stacks of that Berry Pouch already
|
||||
const existing = globalScene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerParty()[0], shellBell);
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", {
|
||||
modifierName: shellBell.name,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
} else {
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.HEALING_CHARM));
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.BERRY_POUCH));
|
||||
doEventReward();
|
||||
}
|
||||
}
|
||||
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(it => {
|
||||
return (
|
||||
!OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable
|
||||
);
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has valid item, it can be selected
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Check if the player has max stacks of Healing Charm already
|
||||
const existing = globalScene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier;
|
||||
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerParty()[0], shellBell);
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", { modifierName: shellBell.name }),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
doEventReward();
|
||||
} else {
|
||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.HEALING_CHARM));
|
||||
doEventReward();
|
||||
}
|
||||
|
||||
chosenPokemon.loseHeldItem(modifier, false);
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
|
|
@ -8,9 +8,7 @@ import { randSeedInt } from "#app/utils";
|
|||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import {
|
||||
MysteryEncounterBuilder,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
|
||||
|
@ -22,145 +20,158 @@ const namespace = "mysteryEncounters/departmentStoreSale";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3797 | GitHub Issue #3797}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const DepartmentStoreSaleEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "department_store_sale_lady",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: -20,
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.FURFROU,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 30,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
},
|
||||
async () => {
|
||||
// Choose TMs
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 5) {
|
||||
// 2/2/1 weight on TM rarity
|
||||
const roll = randSeedInt(5);
|
||||
if (roll < 2) {
|
||||
modifiers.push(modifierTypes.TM_COMMON);
|
||||
} else if (roll < 4) {
|
||||
modifiers.push(modifierTypes.TM_GREAT);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.TM_ULTRA);
|
||||
}
|
||||
i++;
|
||||
export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.DEPARTMENT_STORE_SALE,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "department_store_sale_lady",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: -20,
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.FURFROU,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 30,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
},
|
||||
async () => {
|
||||
// Choose TMs
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 5) {
|
||||
// 2/2/1 weight on TM rarity
|
||||
const roll = randSeedInt(5);
|
||||
if (roll < 2) {
|
||||
modifiers.push(modifierTypes.TM_COMMON);
|
||||
} else if (roll < 4) {
|
||||
modifiers.push(modifierTypes.TM_GREAT);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.TM_ULTRA);
|
||||
}
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
|
||||
leaveEncounterWithoutBattle();
|
||||
i++;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
},
|
||||
async () => {
|
||||
// Choose Vitamins
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 3) {
|
||||
// 2/1 weight on base stat booster vs PP Up
|
||||
const roll = randSeedInt(3);
|
||||
if (roll === 0) {
|
||||
modifiers.push(modifierTypes.PP_UP);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.BASE_STAT_BOOSTER);
|
||||
}
|
||||
i++;
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
},
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
},
|
||||
async () => {
|
||||
// Choose Vitamins
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 3) {
|
||||
// 2/1 weight on base stat booster vs PP Up
|
||||
const roll = randSeedInt(3);
|
||||
if (roll === 0) {
|
||||
modifiers.push(modifierTypes.PP_UP);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.BASE_STAT_BOOSTER);
|
||||
}
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
|
||||
leaveEncounterWithoutBattle();
|
||||
i++;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
},
|
||||
async () => {
|
||||
// Choose X Items
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 5) {
|
||||
// 4/1 weight on base stat booster vs Dire Hit
|
||||
const roll = randSeedInt(5);
|
||||
if (roll === 0) {
|
||||
modifiers.push(modifierTypes.DIRE_HIT);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.TEMP_STAT_STAGE_BOOSTER);
|
||||
}
|
||||
i++;
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
},
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
},
|
||||
async () => {
|
||||
// Choose X Items
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 5) {
|
||||
// 4/1 weight on base stat booster vs Dire Hit
|
||||
const roll = randSeedInt(5);
|
||||
if (roll === 0) {
|
||||
modifiers.push(modifierTypes.DIRE_HIT);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.TEMP_STAT_STAGE_BOOSTER);
|
||||
}
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
|
||||
leaveEncounterWithoutBattle();
|
||||
i++;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.4.label`,
|
||||
buttonTooltip: `${namespace}:option.4.tooltip`,
|
||||
},
|
||||
async () => {
|
||||
// Choose Pokeballs
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 4) {
|
||||
// 10/30/20/5 weight on pokeballs
|
||||
const roll = randSeedInt(65);
|
||||
if (roll < 10) {
|
||||
modifiers.push(modifierTypes.POKEBALL);
|
||||
} else if (roll < 40) {
|
||||
modifiers.push(modifierTypes.GREAT_BALL);
|
||||
} else if (roll < 60) {
|
||||
modifiers.push(modifierTypes.ULTRA_BALL);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.ROGUE_BALL);
|
||||
}
|
||||
i++;
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
},
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.4.label`,
|
||||
buttonTooltip: `${namespace}:option.4.tooltip`,
|
||||
},
|
||||
async () => {
|
||||
// Choose Pokeballs
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 4) {
|
||||
// 10/30/20/5 weight on pokeballs
|
||||
const roll = randSeedInt(65);
|
||||
if (roll < 10) {
|
||||
modifiers.push(modifierTypes.POKEBALL);
|
||||
} else if (roll < 40) {
|
||||
modifiers.push(modifierTypes.GREAT_BALL);
|
||||
} else if (roll < 60) {
|
||||
modifiers.push(modifierTypes.ULTRA_BALL);
|
||||
} else {
|
||||
modifiers.push(modifierTypes.ROGUE_BALL);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
|
||||
leaveEncounterWithoutBattle();
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
}
|
||||
])
|
||||
.build();
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: modifiers,
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
},
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierTypeOption,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
setEncounterExp,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
|
@ -22,180 +28,187 @@ const namespace = "mysteryEncounters/fieldTrip";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3794 | GitHub Issue #3794}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const FieldTripEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "preschooler_m",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "field_trip_teacher",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "preschooler_f",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for Pokemon move valid for this option
|
||||
return pokemon.moveset.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and move selected
|
||||
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:physical`));
|
||||
pokemonAndMoveChosen(pokemon, move, MoveCategory.PHYSICAL);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.FIELD_TRIP,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "preschooler_m",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "field_trip_teacher",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "preschooler_f",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for Pokemon move valid for this option
|
||||
return pokemon.moveset.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and move selected
|
||||
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:physical`));
|
||||
pokemonAndMoveChosen(pokemon, move, MoveCategory.PHYSICAL);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ATK ])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.DEF ])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!,
|
||||
generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
];
|
||||
return selectPokemonForOption(onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.ATK])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.DEF])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
];
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
|
||||
}
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifiers,
|
||||
fillRemaining: false,
|
||||
});
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for Pokemon move valid for this option
|
||||
return pokemon.moveset.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and move selected
|
||||
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:special`));
|
||||
pokemonAndMoveChosen(pokemon, move, MoveCategory.SPECIAL);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for Pokemon move valid for this option
|
||||
return pokemon.moveset.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and move selected
|
||||
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:special`));
|
||||
pokemonAndMoveChosen(pokemon, move, MoveCategory.SPECIAL);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPATK ])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPDEF ])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!,
|
||||
generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
];
|
||||
return selectPokemonForOption(onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPATK])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPDEF])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
];
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
|
||||
}
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifiers,
|
||||
fillRemaining: false,
|
||||
});
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for Pokemon move valid for this option
|
||||
return pokemon.moveset.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and move selected
|
||||
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:status`));
|
||||
pokemonAndMoveChosen(pokemon, move, MoveCategory.STATUS);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:second_option_prompt`,
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for Pokemon move valid for this option
|
||||
return pokemon.moveset.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
// Pokemon and move selected
|
||||
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:status`));
|
||||
pokemonAndMoveChosen(pokemon, move, MoveCategory.STATUS);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ACC ])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!,
|
||||
generateModifierTypeOption(modifierTypes.GREAT_BALL)!,
|
||||
generateModifierTypeOption(modifierTypes.IV_SCANNER)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
];
|
||||
return selectPokemonForOption(onPokemonSelected);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.misc.correctMove) {
|
||||
const modifiers = [
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.ACC])!,
|
||||
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
|
||||
generateModifierTypeOption(modifierTypes.GREAT_BALL)!,
|
||||
generateModifierTypeOption(modifierTypes.IV_SCANNER)!,
|
||||
generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
|
||||
];
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
|
||||
}
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: modifiers,
|
||||
fillRemaining: false,
|
||||
});
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
leaveEncounterWithoutBattle(!encounter.misc.correctMove);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
||||
function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correctMoveCategory: MoveCategory) {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
@ -215,7 +228,10 @@ function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correct
|
|||
text: `${namespace}:incorrect_exp`,
|
||||
},
|
||||
];
|
||||
setEncounterExp(globalScene.getPlayerParty().map((p) => p.id), 50);
|
||||
setEncounterExp(
|
||||
globalScene.getPlayerParty().map(p => p.id),
|
||||
50,
|
||||
);
|
||||
} else {
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
{
|
||||
|
@ -229,7 +245,7 @@ function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correct
|
|||
text: `${namespace}:correct_exp`,
|
||||
},
|
||||
];
|
||||
setEncounterExp([ pokemon.id ], 100);
|
||||
setEncounterExp([pokemon.id], 100);
|
||||
}
|
||||
encounter.misc = {
|
||||
correctMove: correctMove,
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
initBattleWithEnemyConfig,
|
||||
loadCustomMovesForEncounter,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterExp,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
generateModifierType,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes, } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { AbilityRequirement, CombinationPokemonRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import {
|
||||
AbilityRequirement,
|
||||
CombinationPokemonRequirement,
|
||||
TypeRequirement,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { Species } from "#enums/species";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Gender } from "#app/data/gender";
|
||||
|
@ -21,7 +33,11 @@ import { WeatherType } from "#enums/weather-type";
|
|||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { applyAbilityOverrideToPokemon, applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
applyAbilityOverrideToPokemon,
|
||||
applyDamageToPokemon,
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
|
@ -48,216 +64,230 @@ const DAMAGE_PERCENTAGE: number = 20;
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3814 | GitHub Issue #3814}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const FieryFalloutEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIERY_FALLOUT)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(40, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withCatchAllowed(true)
|
||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||
.withAnimations(EncounterAnim.MAGMA_BG, EncounterAnim.MAGMA_SPOUT)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroDialogue([
|
||||
export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.FIERY_FALLOUT,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(40, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withCatchAllowed(true)
|
||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||
.withAnimations(EncounterAnim.MAGMA_BG, EncounterAnim.MAGMA_SPOUT)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Calculate boss mons
|
||||
const volcaronaSpecies = getPokemonSpecies(Species.VOLCARONA);
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: volcaronaSpecies,
|
||||
isBoss: false,
|
||||
gender: Gender.MALE,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
globalScene.unshiftPhase(
|
||||
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1),
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
species: volcaronaSpecies,
|
||||
isBoss: false,
|
||||
gender: Gender.FEMALE,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
globalScene.unshiftPhase(
|
||||
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1),
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
doubleBattle: true,
|
||||
disableSwitch: true,
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
// Load hidden Volcarona sprites
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.VOLCARONA,
|
||||
repeat: true,
|
||||
hidden: true,
|
||||
hasShadow: true,
|
||||
x: -20,
|
||||
startFrame: 20,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.VOLCARONA,
|
||||
repeat: true,
|
||||
hidden: true,
|
||||
hasShadow: true,
|
||||
x: 20,
|
||||
},
|
||||
];
|
||||
|
||||
// Load animations/sfx for Volcarona moves
|
||||
loadCustomMovesForEncounter([Moves.FIRE_SPIN, Moves.QUIVER_DANCE]);
|
||||
|
||||
globalScene.arena.trySetWeather(WeatherType.SUNNY, true);
|
||||
|
||||
encounter.setDialogueToken("volcaronaName", getPokemonSpecies(Species.VOLCARONA).getName());
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
// Play animations
|
||||
const background = new EncounterBattleAnim(
|
||||
EncounterAnim.MAGMA_BG,
|
||||
globalScene.getPlayerPokemon()!,
|
||||
globalScene.getPlayerPokemon(),
|
||||
);
|
||||
background.playWithoutTargets(200, 70, 2, 3);
|
||||
const animation = new EncounterBattleAnim(
|
||||
EncounterAnim.MAGMA_SPOUT,
|
||||
globalScene.getPlayerPokemon()!,
|
||||
globalScene.getPlayerPokemon(),
|
||||
);
|
||||
animation.playWithoutTargets(80, 100, 2);
|
||||
globalScene.time.delayedCall(600, () => {
|
||||
animation.playWithoutTargets(-20, 100, 2);
|
||||
});
|
||||
globalScene.time.delayedCall(1200, () => {
|
||||
animation.playWithoutTargets(140, 150, 2);
|
||||
});
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem());
|
||||
|
||||
// Calculate boss mons
|
||||
const volcaronaSpecies = getPokemonSpecies(Species.VOLCARONA);
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: volcaronaSpecies,
|
||||
isBoss: false,
|
||||
gender: Gender.MALE,
|
||||
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1));
|
||||
}
|
||||
},
|
||||
{
|
||||
species: volcaronaSpecies,
|
||||
isBoss: false,
|
||||
gender: Gender.FEMALE,
|
||||
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1));
|
||||
}
|
||||
}
|
||||
],
|
||||
doubleBattle: true,
|
||||
disableSwitch: true,
|
||||
};
|
||||
encounter.enemyPartyConfigs = [ config ];
|
||||
|
||||
// Load hidden Volcarona sprites
|
||||
encounter.spriteConfigs = [
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.VOLCARONA,
|
||||
repeat: true,
|
||||
hidden: true,
|
||||
hasShadow: true,
|
||||
x: -20,
|
||||
startFrame: 20
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.FIRE_SPIN),
|
||||
ignorePp: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "",
|
||||
fileRoot: "",
|
||||
species: Species.VOLCARONA,
|
||||
repeat: true,
|
||||
hidden: true,
|
||||
hasShadow: true,
|
||||
x: 20
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [BattlerIndex.PLAYER_2],
|
||||
move: new PokemonMove(Moves.FIRE_SPIN),
|
||||
ignorePp: true,
|
||||
},
|
||||
];
|
||||
);
|
||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
},
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Damage non-fire types and burn 1 random non-fire type member + give it Heatproof
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const nonFireTypes = globalScene
|
||||
.getPlayerParty()
|
||||
.filter(p => p.isAllowedInBattle() && !p.getTypes().includes(PokemonType.FIRE));
|
||||
|
||||
// Load animations/sfx for Volcarona moves
|
||||
loadCustomMovesForEncounter([ Moves.FIRE_SPIN, Moves.QUIVER_DANCE ]);
|
||||
for (const pkm of nonFireTypes) {
|
||||
const percentage = DAMAGE_PERCENTAGE / 100;
|
||||
const damage = Math.floor(pkm.getMaxHp() * percentage);
|
||||
applyDamageToPokemon(pkm, damage);
|
||||
}
|
||||
|
||||
globalScene.arena.trySetWeather(WeatherType.SUNNY, true);
|
||||
// Burn random member
|
||||
const burnable = nonFireTypes.filter(
|
||||
p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status.effect) || p.status.effect === StatusEffect.NONE,
|
||||
);
|
||||
if (burnable?.length > 0) {
|
||||
const roll = randSeedInt(burnable.length);
|
||||
const chosenPokemon = burnable[roll];
|
||||
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
|
||||
// Burn applied
|
||||
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
|
||||
encounter.setDialogueToken("abilityName", new Ability(Abilities.HEATPROOF, 3).name);
|
||||
queueEncounterMessage(`${namespace}:option.2.target_burned`);
|
||||
|
||||
encounter.setDialogueToken("volcaronaName", getPokemonSpecies(Species.VOLCARONA).getName());
|
||||
// Also permanently change the burned Pokemon's ability to Heatproof
|
||||
applyAbilityOverrideToPokemon(chosenPokemon, Abilities.HEATPROOF);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
// Play animations
|
||||
const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
|
||||
background.playWithoutTargets(200, 70, 2, 3);
|
||||
const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon());
|
||||
animation.playWithoutTargets(80, 100, 2);
|
||||
globalScene.time.delayedCall(600, () => {
|
||||
animation.playWithoutTargets(-20, 100, 2);
|
||||
});
|
||||
globalScene.time.delayedCall(1200, () => {
|
||||
animation.playWithoutTargets(140, 150, 2);
|
||||
});
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
// No rewards
|
||||
leaveEncounterWithoutBattle(true);
|
||||
},
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
new TypeRequirement(PokemonType.FIRE, true, 1),
|
||||
new AbilityRequirement(FIRE_RESISTANT_ABILITIES, true),
|
||||
),
|
||||
) // Will set option3PrimaryName dialogue token automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Do NOT await this, to prevent player from repeatedly pressing options
|
||||
transitionMysteryEncounterIntroVisuals(false, false, 2000);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Fire types help calm the Volcarona
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem());
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, () => {
|
||||
giveLeadPokemonAttackTypeBoostItem();
|
||||
});
|
||||
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [ BattlerIndex.PLAYER ],
|
||||
move: new PokemonMove(Moves.FIRE_SPIN),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [ BattlerIndex.PLAYER_2 ],
|
||||
move: new PokemonMove(Moves.FIRE_SPIN),
|
||||
ignorePp: true
|
||||
});
|
||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Damage non-fire types and burn 1 random non-fire type member + give it Heatproof
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const nonFireTypes = globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(PokemonType.FIRE));
|
||||
const primary = encounter.options[2].primaryPokemon!;
|
||||
|
||||
for (const pkm of nonFireTypes) {
|
||||
const percentage = DAMAGE_PERCENTAGE / 100;
|
||||
const damage = Math.floor(pkm.getMaxHp() * percentage);
|
||||
applyDamageToPokemon(pkm, damage);
|
||||
}
|
||||
|
||||
// Burn random member
|
||||
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status.effect) || p.status.effect === StatusEffect.NONE);
|
||||
if (burnable?.length > 0) {
|
||||
const roll = randSeedInt(burnable.length);
|
||||
const chosenPokemon = burnable[roll];
|
||||
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
|
||||
// Burn applied
|
||||
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
|
||||
encounter.setDialogueToken("abilityName", new Ability(Abilities.HEATPROOF, 3).name);
|
||||
queueEncounterMessage(`${namespace}:option.2.target_burned`);
|
||||
|
||||
// Also permanently change the burned Pokemon's ability to Heatproof
|
||||
applyAbilityOverrideToPokemon(chosenPokemon, Abilities.HEATPROOF);
|
||||
}
|
||||
}
|
||||
|
||||
// No rewards
|
||||
leaveEncounterWithoutBattle(true);
|
||||
}
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(
|
||||
CombinationPokemonRequirement.Some(
|
||||
new TypeRequirement(PokemonType.FIRE, true, 1),
|
||||
new AbilityRequirement(FIRE_RESISTANT_ABILITIES, true)
|
||||
)
|
||||
) // Will set option3PrimaryName dialogue token automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Do NOT await this, to prevent player from repeatedly pressing options
|
||||
transitionMysteryEncounterIntroVisuals(false, false, 2000);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Fire types help calm the Volcarona
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
setEncounterRewards(
|
||||
{ fillRemaining: true },
|
||||
undefined,
|
||||
() => {
|
||||
giveLeadPokemonAttackTypeBoostItem();
|
||||
});
|
||||
|
||||
const primary = encounter.options[2].primaryPokemon!;
|
||||
|
||||
setEncounterExp([ primary.id ], getPokemonSpecies(Species.VOLCARONA).baseExp * 2);
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
setEncounterExp([primary.id], getPokemonSpecies(Species.VOLCARONA).baseExp * 2);
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
||||
function giveLeadPokemonAttackTypeBoostItem() {
|
||||
// Give first party pokemon attack type boost item for free at end of battle
|
||||
|
@ -266,7 +296,9 @@ function giveLeadPokemonAttackTypeBoostItem() {
|
|||
// Generate type booster held item, default to Charcoal if item fails to generate
|
||||
let boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType;
|
||||
if (!boosterModifierType) {
|
||||
boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ PokemonType.FIRE ]) as AttackTypeBoosterModifierType;
|
||||
boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
|
||||
PokemonType.FIRE,
|
||||
]) as AttackTypeBoosterModifierType;
|
||||
}
|
||||
applyModifierTypeToPlayerPokemon(leadPokemon, boosterModifierType);
|
||||
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import type {
|
||||
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
getRandomEncounterSpecies,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterExp,
|
||||
setEncounterRewards
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import type {
|
||||
ModifierTypeOption } from "#app/modifier/modifier-type";
|
||||
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
|
||||
import {
|
||||
getPlayerModifierTypeOptions,
|
||||
ModifierPoolType,
|
||||
|
@ -25,7 +23,11 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
|
|||
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { getEncounterPokemonLevelForWave, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
getEncounterPokemonLevelForWave,
|
||||
getSpriteKeysFromPokemon,
|
||||
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
@ -41,152 +43,163 @@ const namespace = "mysteryEncounters/fightOrFlight";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3795 | GitHub Issue #3795}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const FightOrFlightEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.FIGHT_OR_FLIGHT,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Calculate boss mon
|
||||
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
|
||||
const bossPokemon = getRandomEncounterSpecies(level, true);
|
||||
encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender());
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [{
|
||||
// Calculate boss mon
|
||||
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
|
||||
const bossPokemon = getRandomEncounterSpecies(level, true);
|
||||
encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender());
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [
|
||||
{
|
||||
level: level,
|
||||
species: bossPokemon.species,
|
||||
dataSource: new PokemonData(bossPokemon),
|
||||
isBoss: true,
|
||||
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.1.stat_boost`);
|
||||
// Randomly boost 1 stat 2 stages
|
||||
// Cannot boost Spd, Acc, or Evasion
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ randSeedInt(4, 1) ], 2));
|
||||
}
|
||||
}],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [ config ];
|
||||
|
||||
// Calculate item
|
||||
// Waves 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER
|
||||
const tier =
|
||||
globalScene.currentBattle.waveIndex > 160
|
||||
? ModifierTier.MASTER
|
||||
: globalScene.currentBattle.waveIndex > 120
|
||||
? ModifierTier.ROGUE
|
||||
: globalScene.currentBattle.waveIndex > 40
|
||||
? ModifierTier.ULTRA
|
||||
: ModifierTier.GREAT;
|
||||
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
|
||||
let item: ModifierTypeOption | null = null;
|
||||
// TMs and Candy Jar excluded from possible rewards as they're too swingy in value for a singular item reward
|
||||
while (!item || item.type.id.includes("TM_") || item.type.id === "CANDY_JAR") {
|
||||
item = getPlayerModifierTypeOptions(1, globalScene.getPlayerParty(), [], { guaranteedModifierTiers: [ tier ], allowLuckUpgrades: false })[0];
|
||||
}
|
||||
encounter.setDialogueToken("itemName", item.type.name);
|
||||
encounter.misc = item;
|
||||
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: item.type.iconImage,
|
||||
fileRoot: "items",
|
||||
hasShadow: false,
|
||||
x: 35,
|
||||
y: -5,
|
||||
scale: 0.75,
|
||||
isItem: true,
|
||||
disableAnimation: true
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2));
|
||||
},
|
||||
},
|
||||
{
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
tint: 0.25,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
isShiny: bossPokemon.shiny,
|
||||
variant: bossPokemon.variant
|
||||
},
|
||||
];
|
||||
],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
// Calculate item
|
||||
// Waves 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER
|
||||
const tier =
|
||||
globalScene.currentBattle.waveIndex > 160
|
||||
? ModifierTier.MASTER
|
||||
: globalScene.currentBattle.waveIndex > 120
|
||||
? ModifierTier.ROGUE
|
||||
: globalScene.currentBattle.waveIndex > 40
|
||||
? ModifierTier.ULTRA
|
||||
: ModifierTier.GREAT;
|
||||
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
|
||||
let item: ModifierTypeOption | null = null;
|
||||
// TMs and Candy Jar excluded from possible rewards as they're too swingy in value for a singular item reward
|
||||
while (!item || item.type.id.includes("TM_") || item.type.id === "CANDY_JAR") {
|
||||
item = getPlayerModifierTypeOptions(1, globalScene.getPlayerParty(), [], {
|
||||
guaranteedModifierTiers: [tier],
|
||||
allowLuckUpgrades: false,
|
||||
})[0];
|
||||
}
|
||||
encounter.setDialogueToken("itemName", item.type.name);
|
||||
encounter.misc = item;
|
||||
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
spriteKey: item.type.iconImage,
|
||||
fileRoot: "items",
|
||||
hasShadow: false,
|
||||
x: 35,
|
||||
y: -5,
|
||||
scale: 0.75,
|
||||
isItem: true,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
tint: 0.25,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
isShiny: bossPokemon.shiny,
|
||||
variant: bossPokemon.variant,
|
||||
},
|
||||
];
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
// Pokemon will randomly boost 1 stat by 2 stages
|
||||
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [item],
|
||||
fillRemaining: false,
|
||||
});
|
||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
},
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
// Pokemon will randomly boost 1 stat by 2 stages
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Pick steal
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false });
|
||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`
|
||||
}
|
||||
]
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Pick steal
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false });
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [item],
|
||||
fillRemaining: false,
|
||||
});
|
||||
|
||||
// Use primaryPokemon to execute the thievery
|
||||
const primaryPokemon = encounter.options[1].primaryPokemon!;
|
||||
setEncounterExp(primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
// Use primaryPokemon to execute the thievery
|
||||
const primaryPokemon = encounter.options[1].primaryPokemon!;
|
||||
setEncounterExp(primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
updatePlayerMoney,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
|
@ -35,64 +41,65 @@ const namespace = "mysteryEncounters/funAndGames";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3819 | GitHub Issue #3819}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const FunAndGamesEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FUN_AND_GAMES)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion to play
|
||||
.withAutoHideIntroVisuals(false)
|
||||
// The Wobbuffet won't use moves
|
||||
.withSkipEnemyBattleTurns(true)
|
||||
// Will skip COMMAND selection menu and go straight to FIGHT (move select) menu
|
||||
.withSkipToFightInput(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "fun_and_games_game",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
x: 0,
|
||||
y: 6,
|
||||
},
|
||||
{
|
||||
spriteKey: "fun_and_games_wobbuffet",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: -28,
|
||||
y: 6,
|
||||
yShadow: 6
|
||||
},
|
||||
{
|
||||
spriteKey: "fun_and_games_man",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: 40,
|
||||
y: 6,
|
||||
yShadow: 6
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
globalScene.loadBgm("mystery_encounter_fun_and_games", "mystery_encounter_fun_and_games.mp3");
|
||||
encounter.setDialogueToken("wobbuffetName", getPokemonSpecies(Species.WOBBUFFET).getName());
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
globalScene.fadeAndSwitchBgm("mystery_encounter_fun_and_games");
|
||||
return true;
|
||||
})
|
||||
.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
export const FunAndGamesEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.FUN_AND_GAMES,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion to play
|
||||
.withAutoHideIntroVisuals(false)
|
||||
// The Wobbuffet won't use moves
|
||||
.withSkipEnemyBattleTurns(true)
|
||||
// Will skip COMMAND selection menu and go straight to FIGHT (move select) menu
|
||||
.withSkipToFightInput(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "fun_and_games_game",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
x: 0,
|
||||
y: 6,
|
||||
},
|
||||
{
|
||||
spriteKey: "fun_and_games_wobbuffet",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: -28,
|
||||
y: 6,
|
||||
yShadow: 6,
|
||||
},
|
||||
{
|
||||
spriteKey: "fun_and_games_man",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: 40,
|
||||
y: 6,
|
||||
yShadow: 6,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
globalScene.loadBgm("mystery_encounter_fun_and_games", "mystery_encounter_fun_and_games.mp3");
|
||||
encounter.setDialogueToken("wobbuffetName", getPokemonSpecies(Species.WOBBUFFET).getName());
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
globalScene.fadeAndSwitchBgm("mystery_encounter_fun_and_games");
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
|
@ -127,7 +134,11 @@ export const FunAndGamesEncounter: MysteryEncounter =
|
|||
// Update money
|
||||
const moneyCost = (encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney;
|
||||
updatePlayerMoney(-moneyCost, true, false);
|
||||
await showEncounterText(i18next.t("mysteryEncounterMessages:paid_money", { amount: moneyCost }));
|
||||
await showEncounterText(
|
||||
i18next.t("mysteryEncounterMessages:paid_money", {
|
||||
amount: moneyCost,
|
||||
}),
|
||||
);
|
||||
|
||||
// Handlers for battle events
|
||||
encounter.onTurnStart = handleNextTurn; // triggered during TurnInitPhase
|
||||
|
@ -139,28 +150,29 @@ export const FunAndGamesEncounter: MysteryEncounter =
|
|||
|
||||
return true;
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
await transitionMysteryEncounterIntroVisuals(true, true);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
await transitionMysteryEncounterIntroVisuals(true, true);
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
async function summonPlayerPokemon() {
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
|
||||
return new Promise<void>(async resolve => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
|
@ -176,9 +188,15 @@ async function summonPlayerPokemon() {
|
|||
|
||||
// Do trainer summon animation
|
||||
let playerAnimationPromise: Promise<void> | undefined;
|
||||
globalScene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(playerPokemon) }));
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:playerGo", {
|
||||
pokemonName: getPokemonNameWithAffix(playerPokemon),
|
||||
}),
|
||||
);
|
||||
globalScene.pbTray.hide();
|
||||
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
|
||||
globalScene.trainer.setTexture(
|
||||
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`,
|
||||
);
|
||||
globalScene.time.delayedCall(562, () => {
|
||||
globalScene.trainer.setFrame("2");
|
||||
globalScene.time.delayedCall(64, () => {
|
||||
|
@ -189,7 +207,7 @@ async function summonPlayerPokemon() {
|
|||
targets: globalScene.trainer,
|
||||
x: -36,
|
||||
duration: 1000,
|
||||
onComplete: () => globalScene.trainer.setVisible(false)
|
||||
onComplete: () => globalScene.trainer.setVisible(false),
|
||||
});
|
||||
globalScene.time.delayedCall(750, () => {
|
||||
playerAnimationPromise = summonPlayerPokemonAnimation(playerPokemon);
|
||||
|
@ -198,8 +216,14 @@ async function summonPlayerPokemon() {
|
|||
// Also loads Wobbuffet data (cannot be shiny)
|
||||
const enemySpecies = getPokemonSpecies(Species.WOBBUFFET);
|
||||
globalScene.currentBattle.enemyParty = [];
|
||||
const wobbuffet = globalScene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false, true);
|
||||
wobbuffet.ivs = [ 0, 0, 0, 0, 0, 0 ];
|
||||
const wobbuffet = globalScene.addEnemyPokemon(
|
||||
enemySpecies,
|
||||
encounter.misc.playerPokemon.level,
|
||||
TrainerSlot.NONE,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
wobbuffet.ivs = [0, 0, 0, 0, 0, 0];
|
||||
wobbuffet.setNature(Nature.MILD);
|
||||
wobbuffet.setAlpha(0);
|
||||
wobbuffet.setVisible(false);
|
||||
|
@ -219,6 +243,7 @@ async function summonPlayerPokemon() {
|
|||
}
|
||||
|
||||
function handleLoseMinigame() {
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
|
||||
return new Promise<void>(async resolve => {
|
||||
// Check Wobbuffet is still alive
|
||||
const wobbuffet = globalScene.getEnemyPokemon();
|
||||
|
@ -258,15 +283,24 @@ function handleNextTurn() {
|
|||
let isHealPhase = false;
|
||||
if (healthRatio < 0.03) {
|
||||
// Grand prize
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.MULTI_LENS ], fillRemaining: false });
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.MULTI_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:best_result`;
|
||||
} else if (healthRatio < 0.15) {
|
||||
// 2nd prize
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SCOPE_LENS ], fillRemaining: false });
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SCOPE_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:great_result`;
|
||||
} else if (healthRatio < 0.33) {
|
||||
// 3rd prize
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.WIDE_LENS ], fillRemaining: false });
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.WIDE_LENS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
resultMessageKey = `${namespace}:good_result`;
|
||||
} else {
|
||||
// No prize
|
||||
|
@ -286,14 +320,13 @@ function handleNextTurn() {
|
|||
|
||||
// Skip remainder of TurnInitPhase
|
||||
return true;
|
||||
} else {
|
||||
if (encounter.misc.turnsRemaining < 3) {
|
||||
// Display charging messages on turns that aren't the initial turn
|
||||
queueEncounterMessage(`${namespace}:charging_continue`);
|
||||
}
|
||||
queueEncounterMessage(`${namespace}:turn_remaining_${encounter.misc.turnsRemaining}`);
|
||||
encounter.misc.turnsRemaining--;
|
||||
}
|
||||
if (encounter.misc.turnsRemaining < 3) {
|
||||
// Display charging messages on turns that aren't the initial turn
|
||||
queueEncounterMessage(`${namespace}:charging_continue`);
|
||||
}
|
||||
queueEncounterMessage(`${namespace}:turn_remaining_${encounter.misc.turnsRemaining}`);
|
||||
encounter.misc.turnsRemaining--;
|
||||
|
||||
// Don't skip remainder of TurnInitPhase
|
||||
return false;
|
||||
|
@ -336,7 +369,7 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
|
|||
globalScene.tweens.add({
|
||||
targets: pokeball,
|
||||
duration: 650,
|
||||
x: 100 + fpOffset[0]
|
||||
x: 100 + fpOffset[0],
|
||||
});
|
||||
|
||||
globalScene.tweens.add({
|
||||
|
@ -387,11 +420,11 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
|
|||
globalScene.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex()));
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -408,14 +441,14 @@ function hideShowmanIntroSprite() {
|
|||
y: "-=16",
|
||||
alpha: 0,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 750
|
||||
duration: 750,
|
||||
});
|
||||
|
||||
// Slide the Wobbuffet and Game over slightly
|
||||
globalScene.tweens.add({
|
||||
targets: [ wobbuffet, carnivalGame ],
|
||||
targets: [wobbuffet, carnivalGame],
|
||||
x: "+=16",
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 750
|
||||
duration: 750,
|
||||
});
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -29,7 +29,9 @@ const namespace = "mysteryEncounters/lostAtSea";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3793 | GitHub Issue #3793}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.LOST_AT_SEA)
|
||||
export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.LOST_AT_SEA,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([
|
||||
|
@ -57,8 +59,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
|||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
// Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
|
@ -72,12 +73,11 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
|||
],
|
||||
})
|
||||
.withOptionPhase(async () => handlePokemonGuidingYouPhase())
|
||||
.build()
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
//Option 2: Use a (non fainted) pokemon that can learn fly to guide you back.
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
|
@ -91,7 +91,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
|||
],
|
||||
})
|
||||
.withOptionPhase(async () => handlePokemonGuidingYouPhase())
|
||||
.build()
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
// Option 3: Wander aimlessly
|
||||
|
@ -105,7 +105,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
|||
],
|
||||
},
|
||||
async () => {
|
||||
const allowedPokemon = globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle());
|
||||
const allowedPokemon = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle());
|
||||
|
||||
for (const pkm of allowedPokemon) {
|
||||
const percentage = DAMAGE_PERCENTAGE / 100;
|
||||
|
@ -116,7 +116,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
|||
leaveEncounterWithoutBattle();
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type {
|
||||
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
initBattleWithEnemyConfig,
|
||||
setEncounterRewards,
|
||||
|
@ -29,195 +28,202 @@ const namespace = "mysteryEncounters/mysteriousChallengers";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3801 | GitHub Issue #3801}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const MysteriousChallengersEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||
.withIntroDialogue([
|
||||
export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.MYSTERIOUS_CHALLENGERS,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Calculates what trainers are available for battle in the encounter
|
||||
|
||||
// Normal difficulty trainer is randomly pulled from biome
|
||||
const normalTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex);
|
||||
const normalConfig = trainerConfigs[normalTrainerType].clone();
|
||||
let female = false;
|
||||
if (normalConfig.hasGenders) {
|
||||
female = !!Utils.randSeedInt(2);
|
||||
}
|
||||
const normalSpriteKey = normalConfig.getSpriteKey(female, normalConfig.doubleOnly);
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: normalConfig,
|
||||
female: female,
|
||||
});
|
||||
|
||||
// Hard difficulty trainer is another random trainer, but with AVERAGE_BALANCED config
|
||||
// Number of mons is based off wave: 1-20 is 2, 20-40 is 3, etc. capping at 6 after wave 100
|
||||
let retries = 0;
|
||||
let hardTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex);
|
||||
while (retries < 5 && hardTrainerType === normalTrainerType) {
|
||||
// Will try to use a different trainer from the normal trainer type
|
||||
hardTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex);
|
||||
retries++;
|
||||
}
|
||||
const hardTemplate = new TrainerPartyCompoundTemplate(
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true),
|
||||
new TrainerPartyTemplate(
|
||||
Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 20), 5),
|
||||
PartyMemberStrength.AVERAGE,
|
||||
false,
|
||||
true,
|
||||
),
|
||||
);
|
||||
const hardConfig = trainerConfigs[hardTrainerType].clone();
|
||||
hardConfig.setPartyTemplates(hardTemplate);
|
||||
female = false;
|
||||
if (hardConfig.hasGenders) {
|
||||
female = !!Utils.randSeedInt(2);
|
||||
}
|
||||
const hardSpriteKey = hardConfig.getSpriteKey(female, hardConfig.doubleOnly);
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: hardConfig,
|
||||
levelAdditiveModifier: 1,
|
||||
female: female,
|
||||
});
|
||||
|
||||
// Brutal trainer is pulled from pool of boss trainers (gym leaders) for the biome
|
||||
// They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons
|
||||
const brutalTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex, true);
|
||||
const e4Template = trainerPartyTemplates.ELITE_FOUR;
|
||||
const brutalConfig = trainerConfigs[brutalTrainerType].clone();
|
||||
brutalConfig.title = trainerConfigs[brutalTrainerType].title;
|
||||
brutalConfig.setPartyTemplates(e4Template);
|
||||
// @ts-ignore
|
||||
brutalConfig.partyTemplateFunc = null; // Overrides gym leader party template func
|
||||
female = false;
|
||||
if (brutalConfig.hasGenders) {
|
||||
female = !!Utils.randSeedInt(2);
|
||||
}
|
||||
const brutalSpriteKey = brutalConfig.getSpriteKey(female, brutalConfig.doubleOnly);
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: brutalConfig,
|
||||
levelAdditiveModifier: 1.5,
|
||||
female: female,
|
||||
});
|
||||
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
spriteKey: normalSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
tint: 1,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
{
|
||||
spriteKey: hardSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
tint: 1,
|
||||
},
|
||||
{
|
||||
spriteKey: brutalSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
tint: 1,
|
||||
},
|
||||
];
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Calculates what trainers are available for battle in the encounter
|
||||
// Spawn standard trainer battle with memory mushroom reward
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
// Normal difficulty trainer is randomly pulled from biome
|
||||
const normalTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex);
|
||||
const normalConfig = trainerConfigs[normalTrainerType].clone();
|
||||
let female = false;
|
||||
if (normalConfig.hasGenders) {
|
||||
female = !!Utils.randSeedInt(2);
|
||||
}
|
||||
const normalSpriteKey = normalConfig.getSpriteKey(female, normalConfig.doubleOnly);
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: normalConfig,
|
||||
female: female,
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM],
|
||||
fillRemaining: true,
|
||||
});
|
||||
|
||||
// Hard difficulty trainer is another random trainer, but with AVERAGE_BALANCED config
|
||||
// Number of mons is based off wave: 1-20 is 2, 20-40 is 3, etc. capping at 6 after wave 100
|
||||
let retries = 0;
|
||||
let hardTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex);
|
||||
while (retries < 5 && hardTrainerType === normalTrainerType) {
|
||||
// Will try to use a different trainer from the normal trainer type
|
||||
hardTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex);
|
||||
retries++;
|
||||
}
|
||||
const hardTemplate = new TrainerPartyCompoundTemplate(
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true),
|
||||
new TrainerPartyTemplate(
|
||||
Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 20), 5),
|
||||
PartyMemberStrength.AVERAGE,
|
||||
false,
|
||||
true
|
||||
)
|
||||
);
|
||||
const hardConfig = trainerConfigs[hardTrainerType].clone();
|
||||
hardConfig.setPartyTemplates(hardTemplate);
|
||||
female = false;
|
||||
if (hardConfig.hasGenders) {
|
||||
female = !!Utils.randSeedInt(2);
|
||||
}
|
||||
const hardSpriteKey = hardConfig.getSpriteKey(female, hardConfig.doubleOnly);
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: hardConfig,
|
||||
levelAdditiveModifier: 1,
|
||||
female: female,
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let initBattlePromise: Promise<void>;
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
initBattlePromise = initBattleWithEnemyConfig(config);
|
||||
}, globalScene.currentBattle.waveIndex * 10);
|
||||
await initBattlePromise!;
|
||||
},
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn hard fight
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
fillRemaining: true,
|
||||
});
|
||||
|
||||
// Brutal trainer is pulled from pool of boss trainers (gym leaders) for the biome
|
||||
// They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons
|
||||
const brutalTrainerType = globalScene.arena.randomTrainerType(
|
||||
globalScene.currentBattle.waveIndex,
|
||||
true
|
||||
);
|
||||
const e4Template = trainerPartyTemplates.ELITE_FOUR;
|
||||
const brutalConfig = trainerConfigs[brutalTrainerType].clone();
|
||||
brutalConfig.title = trainerConfigs[brutalTrainerType].title;
|
||||
brutalConfig.setPartyTemplates(e4Template);
|
||||
// @ts-ignore
|
||||
brutalConfig.partyTemplateFunc = null; // Overrides gym leader party template func
|
||||
female = false;
|
||||
if (brutalConfig.hasGenders) {
|
||||
female = !!Utils.randSeedInt(2);
|
||||
}
|
||||
const brutalSpriteKey = brutalConfig.getSpriteKey(female, brutalConfig.doubleOnly);
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: brutalConfig,
|
||||
levelAdditiveModifier: 1.5,
|
||||
female: female,
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let initBattlePromise: Promise<void>;
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
initBattlePromise = initBattleWithEnemyConfig(config);
|
||||
}, globalScene.currentBattle.waveIndex * 100);
|
||||
await initBattlePromise!;
|
||||
},
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn brutal fight
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[2];
|
||||
|
||||
// To avoid player level snowballing from picking this option
|
||||
encounter.expMultiplier = 0.9;
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT],
|
||||
fillRemaining: true,
|
||||
});
|
||||
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: normalSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
tint: 1,
|
||||
},
|
||||
{
|
||||
spriteKey: hardSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
tint: 1,
|
||||
},
|
||||
{
|
||||
spriteKey: brutalSpriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
tint: 1,
|
||||
},
|
||||
];
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn standard trainer battle with memory mushroom reward
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM ], fillRemaining: true });
|
||||
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let initBattlePromise: Promise<void>;
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
initBattlePromise = initBattleWithEnemyConfig(config);
|
||||
}, globalScene.currentBattle.waveIndex * 10);
|
||||
await initBattlePromise!;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn hard fight
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: true });
|
||||
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let initBattlePromise: Promise<void>;
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
initBattlePromise = initBattleWithEnemyConfig(config);
|
||||
}, globalScene.currentBattle.waveIndex * 100);
|
||||
await initBattlePromise!;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn brutal fight
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[2];
|
||||
|
||||
// To avoid player level snowballing from picking this option
|
||||
encounter.expMultiplier = 0.9;
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true });
|
||||
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let initBattlePromise: Promise<void>;
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
initBattlePromise = initBattleWithEnemyConfig(config);
|
||||
}, globalScene.currentBattle.waveIndex * 1000);
|
||||
await initBattlePromise!;
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
// Seed offsets to remove possibility of different trainers having exact same teams
|
||||
let initBattlePromise: Promise<void>;
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
initBattlePromise = initBattleWithEnemyConfig(config);
|
||||
}, globalScene.currentBattle.waveIndex * 1000);
|
||||
await initBattlePromise!;
|
||||
},
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
|
|
@ -4,8 +4,16 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
|
|||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
getHighestLevelPlayerPokemon,
|
||||
koPlayerPokemon,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
|
@ -32,183 +40,181 @@ const MASTER_REWARDS_PERCENT = 5;
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3796 | GitHub Issue #3796}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const MysteriousChestEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(2, 6, true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withCatchAllowed(true)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "mysterious_chest_blue",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 8,
|
||||
yShadow: 6,
|
||||
alpha: 1,
|
||||
disableAnimation: true, // Re-enabled after option select
|
||||
},
|
||||
{
|
||||
spriteKey: "mysterious_chest_red",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
y: 8,
|
||||
yShadow: 6,
|
||||
alpha: 0,
|
||||
disableAnimation: true, // Re-enabled after option select
|
||||
}
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.MYSTERIOUS_CHEST,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(2, 6, true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withCatchAllowed(true)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "mysterious_chest_blue",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 8,
|
||||
yShadow: 6,
|
||||
alpha: 1,
|
||||
disableAnimation: true, // Re-enabled after option select
|
||||
},
|
||||
{
|
||||
spriteKey: "mysterious_chest_red",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
y: 8,
|
||||
yShadow: 6,
|
||||
alpha: 0,
|
||||
disableAnimation: true, // Re-enabled after option select
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Calculate boss mon
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveModifier: 0.5,
|
||||
disableSwitch: true,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.GIMMIGHOUL),
|
||||
formIndex: 0,
|
||||
isBoss: true,
|
||||
moveSet: [ Moves.NASTY_PLOT, Moves.SHADOW_BALL, Moves.POWER_GEM, Moves.THIEF ]
|
||||
}
|
||||
],
|
||||
};
|
||||
// Calculate boss mon
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveModifier: 0.5,
|
||||
disableSwitch: true,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.GIMMIGHOUL),
|
||||
formIndex: 0,
|
||||
isBoss: true,
|
||||
moveSet: [Moves.NASTY_PLOT, Moves.SHADOW_BALL, Moves.POWER_GEM, Moves.THIEF],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
encounter.enemyPartyConfigs = [ config ];
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
encounter.setDialogueToken("gimmighoulName", getPokemonSpecies(Species.GIMMIGHOUL).getName());
|
||||
encounter.setDialogueToken("trapPercent", TRAP_PERCENT.toString());
|
||||
encounter.setDialogueToken("commonPercent", COMMON_REWARDS_PERCENT.toString());
|
||||
encounter.setDialogueToken("ultraPercent", ULTRA_REWARDS_PERCENT.toString());
|
||||
encounter.setDialogueToken("roguePercent", ROGUE_REWARDS_PERCENT.toString());
|
||||
encounter.setDialogueToken("masterPercent", MASTER_REWARDS_PERCENT.toString());
|
||||
encounter.setDialogueToken("gimmighoulName", getPokemonSpecies(Species.GIMMIGHOUL).getName());
|
||||
encounter.setDialogueToken("trapPercent", TRAP_PERCENT.toString());
|
||||
encounter.setDialogueToken("commonPercent", COMMON_REWARDS_PERCENT.toString());
|
||||
encounter.setDialogueToken("ultraPercent", ULTRA_REWARDS_PERCENT.toString());
|
||||
encounter.setDialogueToken("roguePercent", ROGUE_REWARDS_PERCENT.toString());
|
||||
encounter.setDialogueToken("masterPercent", MASTER_REWARDS_PERCENT.toString());
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Play animation
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const introVisuals = encounter.introVisuals!;
|
||||
|
||||
// Determine roll first
|
||||
const roll = randSeedInt(RAND_LENGTH);
|
||||
encounter.misc = {
|
||||
roll
|
||||
};
|
||||
|
||||
if (roll < TRAP_PERCENT) {
|
||||
// Chest is springing trap, change to red chest sprite
|
||||
const blueChestSprites = introVisuals.getSpriteAtIndex(0);
|
||||
const redChestSprites = introVisuals.getSpriteAtIndex(1);
|
||||
redChestSprites[0].setAlpha(1);
|
||||
blueChestSprites[0].setAlpha(0.001);
|
||||
}
|
||||
introVisuals.spriteConfigs[0].disableAnimation = false;
|
||||
introVisuals.spriteConfigs[1].disableAnimation = false;
|
||||
introVisuals.playAnim();
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Open the chest
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const roll = encounter.misc.roll;
|
||||
if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) {
|
||||
// Choose between 2 COMMON / 2 GREAT tier items (20%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.COMMON,
|
||||
ModifierTier.COMMON,
|
||||
ModifierTier.GREAT,
|
||||
ModifierTier.GREAT,
|
||||
],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.normal`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) {
|
||||
// Choose between 3 ULTRA tier items (30%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.good`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) {
|
||||
// Choose between 2 ROGUE tier items (10%)
|
||||
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE ]});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.great`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT - MASTER_REWARDS_PERCENT) {
|
||||
// Choose 1 MASTER tier item (5%)
|
||||
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.MASTER ]});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.amazing`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else {
|
||||
// Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%)
|
||||
const highestLevelPokemon = getHighestLevelPlayerPokemon(true, false);
|
||||
koPlayerPokemon(highestLevelPokemon);
|
||||
|
||||
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
|
||||
await showEncounterText(`${namespace}:option.1.bad`);
|
||||
|
||||
// Handle game over edge case
|
||||
const allowedPokemon = globalScene.getPokemonAllowedInBattle();
|
||||
if (allowedPokemon.length === 0) {
|
||||
// If there are no longer any legal pokemon in the party, game over.
|
||||
globalScene.clearPhaseQueue();
|
||||
globalScene.unshiftPhase(new GameOverPhase());
|
||||
} else {
|
||||
// Show which Pokemon was KOed, then start battle against Gimmighoul
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
}
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Play animation
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const introVisuals = encounter.introVisuals!;
|
||||
|
||||
// Determine roll first
|
||||
const roll = randSeedInt(RAND_LENGTH);
|
||||
encounter.misc = {
|
||||
roll,
|
||||
};
|
||||
|
||||
if (roll < TRAP_PERCENT) {
|
||||
// Chest is springing trap, change to red chest sprite
|
||||
const blueChestSprites = introVisuals.getSpriteAtIndex(0);
|
||||
const redChestSprites = introVisuals.getSpriteAtIndex(1);
|
||||
redChestSprites[0].setAlpha(1);
|
||||
blueChestSprites[0].setAlpha(0.001);
|
||||
}
|
||||
introVisuals.spriteConfigs[0].disableAnimation = false;
|
||||
introVisuals.spriteConfigs[1].disableAnimation = false;
|
||||
introVisuals.playAnim();
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Open the chest
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const roll = encounter.misc.roll;
|
||||
if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) {
|
||||
// Choose between 2 COMMON / 2 GREAT tier items (20%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.GREAT],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.normal`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) {
|
||||
// Choose between 3 ULTRA tier items (30%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.good`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) {
|
||||
// Choose between 2 ROGUE tier items (10%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.great`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else if (
|
||||
roll >=
|
||||
RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT - MASTER_REWARDS_PERCENT
|
||||
) {
|
||||
// Choose 1 MASTER tier item (5%)
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.MASTER],
|
||||
});
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(`${namespace}:option.1.amazing`);
|
||||
leaveEncounterWithoutBattle();
|
||||
} else {
|
||||
// Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%)
|
||||
const highestLevelPokemon = getHighestLevelPlayerPokemon(true, false);
|
||||
koPlayerPokemon(highestLevelPokemon);
|
||||
|
||||
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
|
||||
await showEncounterText(`${namespace}:option.1.bad`);
|
||||
|
||||
// Handle game over edge case
|
||||
const allowedPokemon = globalScene.getPokemonAllowedInBattle();
|
||||
if (allowedPokemon.length === 0) {
|
||||
// If there are no longer any legal pokemon in the party, game over.
|
||||
globalScene.clearPhaseQueue();
|
||||
globalScene.unshiftPhase(new GameOverPhase());
|
||||
} else {
|
||||
// Show which Pokemon was KOed, then start battle against Gimmighoul
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
setEncounterExp,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
updatePlayerMoney,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
|
@ -24,67 +31,68 @@ const namespace = "mysteryEncounters/partTimer";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3813 | GitHub Issue #3813}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const PartTimerEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.PART_TIMER)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "part_timer_crate",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
y: 6,
|
||||
x: 15
|
||||
},
|
||||
{
|
||||
spriteKey: "worker_f",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: -18,
|
||||
y: 4
|
||||
}
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
// Load sfx
|
||||
globalScene.loadSe("PRSFX- Horn Drill1", "battle_anims", "PRSFX- Horn Drill1.wav");
|
||||
globalScene.loadSe("PRSFX- Horn Drill3", "battle_anims", "PRSFX- Horn Drill3.wav");
|
||||
globalScene.loadSe("PRSFX- Guillotine2", "battle_anims", "PRSFX- Guillotine2.wav");
|
||||
globalScene.loadSe("PRSFX- Heavy Slam2", "battle_anims", "PRSFX- Heavy Slam2.wav");
|
||||
export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.PART_TIMER,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "part_timer_crate",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
y: 6,
|
||||
x: 15,
|
||||
},
|
||||
{
|
||||
spriteKey: "worker_f",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: -18,
|
||||
y: 4,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
// Load sfx
|
||||
globalScene.loadSe("PRSFX- Horn Drill1", "battle_anims", "PRSFX- Horn Drill1.wav");
|
||||
globalScene.loadSe("PRSFX- Horn Drill3", "battle_anims", "PRSFX- Horn Drill3.wav");
|
||||
globalScene.loadSe("PRSFX- Guillotine2", "battle_anims", "PRSFX- Guillotine2.wav");
|
||||
globalScene.loadSe("PRSFX- Heavy Slam2", "battle_anims", "PRSFX- Heavy Slam2.wav");
|
||||
|
||||
globalScene.loadSe("PRSFX- Agility", "battle_anims", "PRSFX- Agility.wav");
|
||||
globalScene.loadSe("PRSFX- Extremespeed1", "battle_anims", "PRSFX- Extremespeed1.wav");
|
||||
globalScene.loadSe("PRSFX- Accelerock1", "battle_anims", "PRSFX- Accelerock1.wav");
|
||||
globalScene.loadSe("PRSFX- Agility", "battle_anims", "PRSFX- Agility.wav");
|
||||
globalScene.loadSe("PRSFX- Extremespeed1", "battle_anims", "PRSFX- Extremespeed1.wav");
|
||||
globalScene.loadSe("PRSFX- Accelerock1", "battle_anims", "PRSFX- Accelerock1.wav");
|
||||
|
||||
globalScene.loadSe("PRSFX- Captivate", "battle_anims", "PRSFX- Captivate.wav");
|
||||
globalScene.loadSe("PRSFX- Attract2", "battle_anims", "PRSFX- Attract2.wav");
|
||||
globalScene.loadSe("PRSFX- Aurora Veil2", "battle_anims", "PRSFX- Aurora Veil2.wav");
|
||||
globalScene.loadSe("PRSFX- Captivate", "battle_anims", "PRSFX- Captivate.wav");
|
||||
globalScene.loadSe("PRSFX- Attract2", "battle_anims", "PRSFX- Attract2.wav");
|
||||
globalScene.loadSe("PRSFX- Aurora Veil2", "battle_anims", "PRSFX- Aurora Veil2.wav");
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`
|
||||
}
|
||||
]
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
@ -95,21 +103,21 @@ export const PartTimerEncounter: MysteryEncounter =
|
|||
// Calculate the "baseline" stat value (90 base stat, 16 IVs, neutral nature, same level as pokemon) to compare
|
||||
// Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
|
||||
// Calculation from Pokemon.calculateStats
|
||||
const baselineValue = Math.floor(((2 * 90 + 16) * pokemon.level) * 0.01) + 5;
|
||||
const baselineValue = Math.floor((2 * 90 + 16) * pokemon.level * 0.01) + 5;
|
||||
const percentDiff = (pokemon.getStat(Stat.SPD) - baselineValue) / baselineValue;
|
||||
const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4);
|
||||
|
||||
encounter.misc = {
|
||||
moneyMultiplier
|
||||
moneyMultiplier,
|
||||
};
|
||||
|
||||
// Reduce all PP to 2 (if they started at greater than 2)
|
||||
pokemon.moveset.forEach(move => {
|
||||
for (const move of pokemon.moveset) {
|
||||
if (move) {
|
||||
const newPpUsed = move.getMovePp() - 2;
|
||||
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setEncounterExp(pokemon.id, 100);
|
||||
|
||||
|
@ -141,24 +149,28 @@ export const PartTimerEncounter: MysteryEncounter =
|
|||
}
|
||||
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
|
||||
updatePlayerMoney(moneyChange, true, false);
|
||||
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
|
||||
await showEncounterText(
|
||||
i18next.t("mysteryEncounterMessages:receive_money", {
|
||||
amount: moneyChange,
|
||||
}),
|
||||
);
|
||||
await showEncounterText(`${namespace}:pokemon_tired`);
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`
|
||||
}
|
||||
]
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
@ -169,24 +181,25 @@ export const PartTimerEncounter: MysteryEncounter =
|
|||
// Calculate the "baseline" stat value (75 base stat, 16 IVs, neutral nature, same level as pokemon) to compare
|
||||
// Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
|
||||
// Calculation from Pokemon.calculateStats
|
||||
const baselineHp = Math.floor(((2 * 75 + 16) * pokemon.level) * 0.01) + pokemon.level + 10;
|
||||
const baselineAtkDef = Math.floor(((2 * 75 + 16) * pokemon.level) * 0.01) + 5;
|
||||
const baselineHp = Math.floor((2 * 75 + 16) * pokemon.level * 0.01) + pokemon.level + 10;
|
||||
const baselineAtkDef = Math.floor((2 * 75 + 16) * pokemon.level * 0.01) + 5;
|
||||
const baselineValue = baselineHp + 1.5 * (baselineAtkDef * 2);
|
||||
const strongestValue = pokemon.getStat(Stat.HP) + 1.5 * (pokemon.getStat(Stat.ATK) + pokemon.getStat(Stat.DEF));
|
||||
const strongestValue =
|
||||
pokemon.getStat(Stat.HP) + 1.5 * (pokemon.getStat(Stat.ATK) + pokemon.getStat(Stat.DEF));
|
||||
const percentDiff = (strongestValue - baselineValue) / baselineValue;
|
||||
const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4);
|
||||
|
||||
encounter.misc = {
|
||||
moneyMultiplier
|
||||
moneyMultiplier,
|
||||
};
|
||||
|
||||
// Reduce all PP to 2 (if they started at greater than 2)
|
||||
pokemon.moveset.forEach(move => {
|
||||
for (const move of pokemon.moveset) {
|
||||
if (move) {
|
||||
const newPpUsed = move.getMovePp() - 2;
|
||||
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setEncounterExp(pokemon.id, 100);
|
||||
|
||||
|
@ -218,73 +231,80 @@ export const PartTimerEncounter: MysteryEncounter =
|
|||
}
|
||||
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
|
||||
updatePlayerMoney(moneyChange, true, false);
|
||||
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
|
||||
await showEncounterText(
|
||||
i18next.t("mysteryEncounterMessages:receive_money", {
|
||||
amount: moneyChange,
|
||||
}),
|
||||
);
|
||||
await showEncounterText(`${namespace}:pokemon_tired`);
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const selectedPokemon = encounter.selectedOption?.primaryPokemon!;
|
||||
encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender());
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const selectedPokemon = encounter.selectedOption?.primaryPokemon!;
|
||||
encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender());
|
||||
|
||||
// Reduce all PP to 2 (if they started at greater than 2)
|
||||
selectedPokemon.moveset.forEach(move => {
|
||||
if (move) {
|
||||
const newPpUsed = move.getMovePp() - 2;
|
||||
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
|
||||
}
|
||||
});
|
||||
// Reduce all PP to 2 (if they started at greater than 2)
|
||||
for (const move of selectedPokemon.moveset) {
|
||||
if (move) {
|
||||
const newPpUsed = move.getMovePp() - 2;
|
||||
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
|
||||
}
|
||||
}
|
||||
|
||||
setEncounterExp(selectedPokemon.id, 100);
|
||||
setEncounterExp(selectedPokemon.id, 100);
|
||||
|
||||
// Hide intro visuals
|
||||
transitionMysteryEncounterIntroVisuals(true, false);
|
||||
// Play sfx for "working"
|
||||
doSalesSfx();
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Assist with Sales
|
||||
// Bring visuals back in
|
||||
await transitionMysteryEncounterIntroVisuals(false, false);
|
||||
// Hide intro visuals
|
||||
transitionMysteryEncounterIntroVisuals(true, false);
|
||||
// Play sfx for "working"
|
||||
doSalesSfx();
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Assist with Sales
|
||||
// Bring visuals back in
|
||||
await transitionMysteryEncounterIntroVisuals(false, false);
|
||||
|
||||
// Give money and do dialogue
|
||||
await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`);
|
||||
const moneyChange = globalScene.getWaveMoneyAmount(2.5);
|
||||
updatePlayerMoney(moneyChange, true, false);
|
||||
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
|
||||
await showEncounterText(`${namespace}:pokemon_tired`);
|
||||
// Give money and do dialogue
|
||||
await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`);
|
||||
const moneyChange = globalScene.getWaveMoneyAmount(2.5);
|
||||
updatePlayerMoney(moneyChange, true, false);
|
||||
await showEncounterText(
|
||||
i18next.t("mysteryEncounterMessages:receive_money", {
|
||||
amount: moneyChange,
|
||||
}),
|
||||
);
|
||||
await showEncounterText(`${namespace}:pokemon_tired`);
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:outro`,
|
||||
}
|
||||
])
|
||||
.build();
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
||||
function doStrongWorkSfx() {
|
||||
globalScene.playSound("battle_anims/PRSFX- Horn Drill1");
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
initSubsequentOptionSelect,
|
||||
leaveEncounterWithoutBattle,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
updatePlayerMoney,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
|
@ -14,7 +19,12 @@ import { NumberHolder, randSeedInt } from "#app/utils";
|
|||
import type PokemonSpecies from "#app/data/pokemon-species";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterCost, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
doPlayerFlee,
|
||||
doPokemonFlee,
|
||||
getRandomSpeciesByStarterCost,
|
||||
trainerThrowPokeball,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
|
@ -27,7 +37,7 @@ import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-gr
|
|||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounters/safariZone";
|
||||
|
||||
const TRAINER_THROW_ANIMATION_TIMES = [ 512, 184, 768 ];
|
||||
const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768];
|
||||
|
||||
const SAFARI_MONEY_MULTIPLIER = 2;
|
||||
|
||||
|
@ -38,36 +48,37 @@ const NUM_SAFARI_ENCOUNTERS = 3;
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3800 | GitHub Issue #3800}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const SafariZoneEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "safari_zone",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
x: 4,
|
||||
y: 6
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
globalScene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString());
|
||||
return true;
|
||||
})
|
||||
.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
export const SafariZoneEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.SAFARI_ZONE,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "safari_zone",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
x: 4,
|
||||
y: 6,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
globalScene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString());
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
|
@ -83,7 +94,7 @@ export const SafariZoneEncounter: MysteryEncounter =
|
|||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
encounter.continuousEncounter = true;
|
||||
encounter.misc = {
|
||||
safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS
|
||||
safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS,
|
||||
};
|
||||
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Load bait/mud assets
|
||||
|
@ -96,28 +107,31 @@ export const SafariZoneEncounter: MysteryEncounter =
|
|||
globalScene.currentBattle.enemyParty = [];
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
await summonSafariPokemon();
|
||||
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, hideDescription: true });
|
||||
initSubsequentOptionSelect({
|
||||
overrideOptions: safariZoneGameOptions,
|
||||
hideDescription: true,
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* SAFARI ZONE MINIGAME OPTIONS
|
||||
|
@ -135,15 +149,14 @@ export const SafariZoneEncounter: MysteryEncounter =
|
|||
* Flee chance = fleeRate / 255
|
||||
*/
|
||||
const safariZoneGameOptions: MysteryEncounterOption[] = [
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:safari.1.label`,
|
||||
buttonTooltip: `${namespace}:safari.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:safari.1.selected`,
|
||||
}
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
|
@ -157,7 +170,11 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
|
|||
// Check how many safari pokemon left
|
||||
if (encounter.misc.safariPokemonRemaining > 0) {
|
||||
await summonSafariPokemon();
|
||||
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true });
|
||||
initSubsequentOptionSelect({
|
||||
overrideOptions: safariZoneGameOptions,
|
||||
startingCursorIndex: 0,
|
||||
hideDescription: true,
|
||||
});
|
||||
} else {
|
||||
// End safari mode
|
||||
encounter.continuousEncounter = false;
|
||||
|
@ -170,8 +187,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
|
|||
return true;
|
||||
})
|
||||
.build(),
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:safari.2.label`,
|
||||
buttonTooltip: `${namespace}:safari.2.tooltip`,
|
||||
|
@ -191,7 +207,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
|
|||
// 80% chance to increase flee stage +1
|
||||
const fleeChangeResult = tryChangeFleeStage(1, 8);
|
||||
if (!fleeChangeResult) {
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.busy_eating`) ?? "", null, 1000, false );
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.busy_eating`) ?? "", null, 1000, false);
|
||||
} else {
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.eating`) ?? "", null, 1000, false);
|
||||
}
|
||||
|
@ -200,8 +216,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
|
|||
return true;
|
||||
})
|
||||
.build(),
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:safari.3.label`,
|
||||
buttonTooltip: `${namespace}:safari.3.tooltip`,
|
||||
|
@ -220,17 +235,16 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
|
|||
// 80% chance to decrease catch stage -1
|
||||
const catchChangeResult = tryChangeCatchStage(-1, 8);
|
||||
if (!catchChangeResult) {
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.beside_itself_angry`) ?? "", null, 1000, false );
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.beside_itself_angry`) ?? "", null, 1000, false);
|
||||
} else {
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.angry`) ?? "", null, 1000, false );
|
||||
await showEncounterText(getEncounterText(`${namespace}:safari.angry`) ?? "", null, 1000, false);
|
||||
}
|
||||
|
||||
await doEndTurn(2);
|
||||
return true;
|
||||
})
|
||||
.build(),
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:safari.4.label`,
|
||||
buttonTooltip: `${namespace}:safari.4.tooltip`,
|
||||
|
@ -243,7 +257,11 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
|
|||
// Check how many safari pokemon left
|
||||
if (encounter.misc.safariPokemonRemaining > 0) {
|
||||
await summonSafariPokemon();
|
||||
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true });
|
||||
initSubsequentOptionSelect({
|
||||
overrideOptions: safariZoneGameOptions,
|
||||
startingCursorIndex: 3,
|
||||
hideDescription: true,
|
||||
});
|
||||
} else {
|
||||
// End safari mode
|
||||
encounter.continuousEncounter = false;
|
||||
|
@ -251,7 +269,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
|
|||
}
|
||||
return true;
|
||||
})
|
||||
.build()
|
||||
.build(),
|
||||
];
|
||||
|
||||
async function summonSafariPokemon() {
|
||||
|
@ -262,38 +280,41 @@ async function summonSafariPokemon() {
|
|||
|
||||
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
|
||||
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
|
||||
let enemySpecies;
|
||||
let pokemon;
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
enemySpecies = getSafariSpeciesSpawn();
|
||||
const level = globalScene.currentBattle.getLevelForWave();
|
||||
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, globalScene.gameMode));
|
||||
pokemon = globalScene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
|
||||
let enemySpecies: PokemonSpecies;
|
||||
let pokemon: any;
|
||||
globalScene.executeWithSeedOffset(
|
||||
() => {
|
||||
enemySpecies = getSafariSpeciesSpawn();
|
||||
const level = globalScene.currentBattle.getLevelForWave();
|
||||
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, globalScene.gameMode));
|
||||
pokemon = globalScene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
|
||||
|
||||
// Roll shiny twice
|
||||
if (!pokemon.shiny) {
|
||||
pokemon.trySetShinySeed();
|
||||
}
|
||||
// Roll shiny twice
|
||||
if (!pokemon.shiny) {
|
||||
pokemon.trySetShinySeed();
|
||||
}
|
||||
|
||||
// Roll HA twice
|
||||
if (pokemon.species.abilityHidden) {
|
||||
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
|
||||
if (pokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new NumberHolder(256);
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
// Roll HA twice
|
||||
if (pokemon.species.abilityHidden) {
|
||||
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
|
||||
if (pokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new NumberHolder(256);
|
||||
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
if (hasHiddenAbility) {
|
||||
pokemon.abilityIndex = hiddenIndex;
|
||||
if (hasHiddenAbility) {
|
||||
pokemon.abilityIndex = hiddenIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pokemon.calculateStats();
|
||||
pokemon.calculateStats();
|
||||
|
||||
globalScene.currentBattle.enemyParty.unshift(pokemon);
|
||||
}, globalScene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining);
|
||||
globalScene.currentBattle.enemyParty.unshift(pokemon);
|
||||
},
|
||||
globalScene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining,
|
||||
);
|
||||
|
||||
globalScene.gameData.setPokemonSeen(pokemon, true);
|
||||
await pokemon.loadAssets();
|
||||
|
@ -324,7 +345,8 @@ function throwPokeball(pokemon: EnemyPokemon): Promise<boolean> {
|
|||
// Catch stage ranges from -6 to +6 (like stat boost stages)
|
||||
const safariCatchStage = globalScene.currentBattle.mysteryEncounter!.misc.catchStage;
|
||||
// Catch modifier ranges from 2/8 (-6 stage) to 8/2 (+6)
|
||||
const safariModifier = (2 + Math.min(Math.max(safariCatchStage, 0), 6)) / (2 - Math.max(Math.min(safariCatchStage, 0), -6));
|
||||
const safariModifier =
|
||||
(2 + Math.min(Math.max(safariCatchStage, 0), 6)) / (2 - Math.max(Math.min(safariCatchStage, 0), -6));
|
||||
// Catch rate same as safari ball
|
||||
const pokeballMultiplier = 1.5;
|
||||
const catchRate = Math.round(baseCatchRate * pokeballMultiplier * safariModifier);
|
||||
|
@ -341,7 +363,9 @@ async function throwBait(pokemon: EnemyPokemon): Promise<boolean> {
|
|||
globalScene.field.add(bait);
|
||||
|
||||
return new Promise(resolve => {
|
||||
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
|
||||
globalScene.trainer.setTexture(
|
||||
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`,
|
||||
);
|
||||
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
|
||||
globalScene.playSound("se/pb_throw");
|
||||
|
||||
|
@ -350,7 +374,9 @@ async function throwBait(pokemon: EnemyPokemon): Promise<boolean> {
|
|||
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
|
||||
globalScene.trainer.setFrame("3");
|
||||
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
|
||||
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
|
||||
globalScene.trainer.setTexture(
|
||||
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -361,7 +387,6 @@ async function throwBait(pokemon: EnemyPokemon): Promise<boolean> {
|
|||
y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" },
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
|
||||
let index = 1;
|
||||
globalScene.time.delayedCall(768, () => {
|
||||
globalScene.tweens.add({
|
||||
|
@ -389,10 +414,10 @@ async function throwBait(pokemon: EnemyPokemon): Promise<boolean> {
|
|||
bait.destroy();
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -407,7 +432,9 @@ async function throwMud(pokemon: EnemyPokemon): Promise<boolean> {
|
|||
globalScene.field.add(mud);
|
||||
|
||||
return new Promise(resolve => {
|
||||
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
|
||||
globalScene.trainer.setTexture(
|
||||
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`,
|
||||
);
|
||||
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => {
|
||||
globalScene.playSound("se/pb_throw");
|
||||
|
||||
|
@ -416,7 +443,9 @@ async function throwMud(pokemon: EnemyPokemon): Promise<boolean> {
|
|||
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => {
|
||||
globalScene.trainer.setFrame("3");
|
||||
globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => {
|
||||
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
|
||||
globalScene.trainer.setTexture(
|
||||
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -461,11 +490,11 @@ async function throwMud(pokemon: EnemyPokemon): Promise<boolean> {
|
|||
},
|
||||
onComplete: () => {
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -474,7 +503,7 @@ async function throwMud(pokemon: EnemyPokemon): Promise<boolean> {
|
|||
function isPokemonFlee(pokemon: EnemyPokemon, fleeStage: number): boolean {
|
||||
const speciesCatchRate = pokemon.species.catchRate;
|
||||
const fleeModifier = (2 + Math.min(Math.max(fleeStage, 0), 6)) / (2 - Math.max(Math.min(fleeStage, 0), -6));
|
||||
const fleeRate = (255 * 255 - speciesCatchRate * speciesCatchRate) / 255 / 2 * fleeModifier;
|
||||
const fleeRate = ((255 * 255 - speciesCatchRate * speciesCatchRate) / 255 / 2) * fleeModifier;
|
||||
console.log("Flee rate: " + fleeRate);
|
||||
const roll = randSeedInt(256);
|
||||
console.log("Roll: " + roll);
|
||||
|
@ -519,7 +548,11 @@ async function doEndTurn(cursorIndex: number) {
|
|||
// Check how many safari pokemon left
|
||||
if (encounter.misc.safariPokemonRemaining > 0) {
|
||||
await summonSafariPokemon();
|
||||
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||
initSubsequentOptionSelect({
|
||||
overrideOptions: safariZoneGameOptions,
|
||||
startingCursorIndex: cursorIndex,
|
||||
hideDescription: true,
|
||||
});
|
||||
} else {
|
||||
// End safari mode
|
||||
encounter.continuousEncounter = false;
|
||||
|
@ -527,7 +560,11 @@ async function doEndTurn(cursorIndex: number) {
|
|||
}
|
||||
} else {
|
||||
globalScene.queueMessage(getEncounterText(`${namespace}:safari.watching`) ?? "", 0, null, 1000);
|
||||
initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||
initSubsequentOptionSelect({
|
||||
overrideOptions: safariZoneGameOptions,
|
||||
startingCursorIndex: cursorIndex,
|
||||
hideDescription: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -535,5 +572,7 @@ async function doEndTurn(cursorIndex: number) {
|
|||
* @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
|
||||
*/
|
||||
export function getSafariSpeciesSpawn(): PokemonSpecies {
|
||||
return getPokemonSpecies(getRandomSpeciesByStarterCost([ 0, 5 ], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false));
|
||||
return getPokemonSpecies(
|
||||
getRandomSpeciesByStarterCost([0, 5], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
setEncounterExp,
|
||||
updatePlayerMoney,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
|
@ -11,7 +17,11 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
|
|||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon, isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
applyDamageToPokemon,
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
isPokemonValidForEncounterOptionSelection,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import type { Nature } from "#enums/nature";
|
||||
|
@ -30,201 +40,204 @@ const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 5;
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3798 | GitHub Issue #3798}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)) // Must have the money for at least the cheap deal
|
||||
.withPrimaryPokemonHealthRatioRequirement([ 0.51, 1 ]) // At least 1 Pokemon must have above half HP
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.KROOKODILE.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 12,
|
||||
y: -5,
|
||||
yShadow: -5
|
||||
},
|
||||
{
|
||||
spriteKey: "shady_vitamin_dealer",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: -12,
|
||||
y: 3,
|
||||
yShadow: 3
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Update money
|
||||
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Calculate modifiers and dialogue tokens
|
||||
const modifiers = [
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
];
|
||||
encounter.setDialogueToken("boost1", modifiers[0].name);
|
||||
encounter.setDialogueToken("boost2", modifiers[1].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
modifiers: modifiers,
|
||||
};
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above half HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
if (!pokemon.isAllowedInChallenge()) {
|
||||
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
|
||||
}
|
||||
if (!encounter.pokemonMeetsPrimaryRequirements(pokemon)) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Choose Cheap Option
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
const modifiers = encounter.misc.modifiers;
|
||||
|
||||
for (const modType of modifiers) {
|
||||
await applyModifierTypeToPlayerPokemon(chosenPokemon, modType);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.withPostOptionPhase(async () => {
|
||||
// Damage and status applied after dealer leaves (to make thematic sense)
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon as PlayerPokemon;
|
||||
|
||||
// Pokemon takes half max HP damage and nature is randomized (does not update dex)
|
||||
applyDamageToPokemon(chosenPokemon, Math.floor(chosenPokemon.getMaxHp() / 2));
|
||||
|
||||
const currentNature = chosenPokemon.nature;
|
||||
let newNature = randSeedInt(25) as Nature;
|
||||
while (newNature === currentNature) {
|
||||
newNature = randSeedInt(25) as Nature;
|
||||
}
|
||||
|
||||
chosenPokemon.setCustomNature(newNature);
|
||||
encounter.setDialogueToken("newNature", getNatureName(newNature));
|
||||
queueEncounterMessage(`${namespace}:cheap_side_effects`);
|
||||
setEncounterExp([ chosenPokemon.id ], 100);
|
||||
await chosenPokemon.updateInfo();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Update money
|
||||
updatePlayerMoney(-(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Calculate modifiers and dialogue tokens
|
||||
const modifiers = [
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
];
|
||||
encounter.setDialogueToken("boost1", modifiers[0].name);
|
||||
encounter.setDialogueToken("boost2", modifiers[1].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
modifiers: modifiers,
|
||||
};
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are unfainted
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Choose Expensive Option
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
const modifiers = encounter.misc.modifiers;
|
||||
|
||||
for (const modType of modifiers) {
|
||||
await applyModifierTypeToPlayerPokemon(chosenPokemon, modType);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.withPostOptionPhase(async () => {
|
||||
// Status applied after dealer leaves (to make thematic sense)
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
queueEncounterMessage(`${namespace}:no_bad_effects`);
|
||||
setEncounterExp([ chosenPokemon.id ], 100);
|
||||
|
||||
await chosenPokemon.updateInfo();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.SHADY_VITAMIN_DEALER,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)) // Must have the money for at least the cheap deal
|
||||
.withPrimaryPokemonHealthRatioRequirement([0.51, 1]) // At least 1 Pokemon must have above half HP
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.KROOKODILE.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 12,
|
||||
y: -5,
|
||||
yShadow: -5,
|
||||
},
|
||||
{
|
||||
spriteKey: "shady_vitamin_dealer",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: -12,
|
||||
y: 3,
|
||||
yShadow: 3,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
speaker: `${namespace}:speaker`
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Update money
|
||||
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Calculate modifiers and dialogue tokens
|
||||
const modifiers = [
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
];
|
||||
encounter.setDialogueToken("boost1", modifiers[0].name);
|
||||
encounter.setDialogueToken("boost2", modifiers[1].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
modifiers: modifiers,
|
||||
};
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above half HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
if (!pokemon.isAllowedInChallenge()) {
|
||||
return (
|
||||
i18next.t("partyUiHandler:cantBeUsed", {
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
}) ?? null
|
||||
);
|
||||
}
|
||||
]
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
if (!encounter.pokemonMeetsPrimaryRequirements(pokemon)) {
|
||||
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Choose Cheap Option
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
const modifiers = encounter.misc.modifiers;
|
||||
|
||||
for (const modType of modifiers) {
|
||||
await applyModifierTypeToPlayerPokemon(chosenPokemon, modType);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
})
|
||||
.withPostOptionPhase(async () => {
|
||||
// Damage and status applied after dealer leaves (to make thematic sense)
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon as PlayerPokemon;
|
||||
|
||||
// Pokemon takes half max HP damage and nature is randomized (does not update dex)
|
||||
applyDamageToPokemon(chosenPokemon, Math.floor(chosenPokemon.getMaxHp() / 2));
|
||||
|
||||
const currentNature = chosenPokemon.nature;
|
||||
let newNature = randSeedInt(25) as Nature;
|
||||
while (newNature === currentNature) {
|
||||
newNature = randSeedInt(25) as Nature;
|
||||
}
|
||||
|
||||
chosenPokemon.setCustomNature(newNature);
|
||||
encounter.setDialogueToken("newNature", getNatureName(newNature));
|
||||
queueEncounterMessage(`${namespace}:cheap_side_effects`);
|
||||
setEncounterExp([chosenPokemon.id], 100);
|
||||
await chosenPokemon.updateInfo();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Update money
|
||||
updatePlayerMoney(-(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Calculate modifiers and dialogue tokens
|
||||
const modifiers = [
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!,
|
||||
];
|
||||
encounter.setDialogueToken("boost1", modifiers[0].name);
|
||||
encounter.setDialogueToken("boost2", modifiers[1].name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
modifiers: modifiers,
|
||||
};
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are unfainted
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Choose Expensive Option
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
const modifiers = encounter.misc.modifiers;
|
||||
|
||||
for (const modType of modifiers) {
|
||||
await applyModifierTypeToPlayerPokemon(chosenPokemon, modType);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.withPostOptionPhase(async () => {
|
||||
// Status applied after dealer leaves (to make thematic sense)
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
queueEncounterMessage(`${namespace}:no_bad_effects`);
|
||||
setEncounterExp([chosenPokemon.id], 100);
|
||||
|
||||
await chosenPokemon.updateInfo();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
|
|
@ -10,7 +10,14 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
|
|||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
|
||||
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
setEncounterExp,
|
||||
setEncounterRewards,
|
||||
} from "../utils/encounter-phase-utils";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
|
@ -31,141 +38,148 @@ const namespace = "mysteryEncounters/slumberingSnorlax";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3815 | GitHub Issue #3815}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const SlumberingSnorlaxEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.SNORLAX.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
tint: 0.25,
|
||||
scale: 1.25,
|
||||
repeat: true,
|
||||
y: 5,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.SLUMBERING_SNORLAX,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.SNORLAX.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
tint: 0.25,
|
||||
scale: 1.25,
|
||||
repeat: true,
|
||||
y: 5,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
console.log(encounter);
|
||||
|
||||
// Calculate boss mon
|
||||
const bossSpecies = getPokemonSpecies(Species.SNORLAX);
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked
|
||||
status: [StatusEffect.SLEEP, 5], // Extra turns on timer for Snorlax's start of fight moves
|
||||
moveSet: [Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
],
|
||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||
aiType: AiType.SMART, // Required to ensure Snorlax uses Sleep Talk while it is asleep
|
||||
};
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveModifier: 0.5,
|
||||
pokemonConfigs: [pokemonConfig],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
// Load animations/sfx for Snorlax fight start moves
|
||||
loadCustomMovesForEncounter([Moves.SNORE]);
|
||||
|
||||
encounter.setDialogueToken("snorlaxName", getPokemonSpecies(Species.SNORLAX).getName());
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
console.log(encounter);
|
||||
|
||||
// Calculate boss mon
|
||||
const bossSpecies = getPokemonSpecies(Species.SNORLAX);
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked
|
||||
status: [ StatusEffect.SLEEP, 5 ], // Extra turns on timer for Snorlax's start of fight moves
|
||||
moveSet: [ Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT ],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2
|
||||
},
|
||||
],
|
||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||
aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep
|
||||
};
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveModifier: 0.5,
|
||||
pokemonConfigs: [ pokemonConfig ],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [ config ];
|
||||
|
||||
// Load animations/sfx for Snorlax fight start moves
|
||||
loadCustomMovesForEncounter([ Moves.SNORE ]);
|
||||
|
||||
encounter.setDialogueToken("snorlaxName", getPokemonSpecies(Species.SNORLAX).getName());
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
|
||||
fillRemaining: true,
|
||||
});
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.SNORE),
|
||||
ignorePp: true,
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.SNORE),
|
||||
ignorePp: true,
|
||||
},
|
||||
);
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
},
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Fall asleep waiting for Snorlax
|
||||
// Full heal party
|
||||
globalScene.unshiftPhase(new PartyHealPhase(true));
|
||||
queueEncounterMessage(`${namespace}:option.2.rest_result`);
|
||||
leaveEncounterWithoutBattle();
|
||||
},
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: true });
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [ BattlerIndex.PLAYER ],
|
||||
move: new PokemonMove(Moves.SNORE),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [ BattlerIndex.PLAYER ],
|
||||
move: new PokemonMove(Moves.SNORE),
|
||||
ignorePp: true
|
||||
});
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Fall asleep waiting for Snorlax
|
||||
// Full heal party
|
||||
globalScene.unshiftPhase(new PartyHealPhase(true));
|
||||
queueEncounterMessage(`${namespace}:option.2.rest_result`);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Steal the Snorlax's Leftovers
|
||||
const instance = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
|
||||
fillRemaining: false,
|
||||
});
|
||||
// Snorlax exp to Pokemon that did the stealing
|
||||
setEncounterExp(instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp);
|
||||
leaveEncounterWithoutBattle();
|
||||
}
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`
|
||||
}
|
||||
]
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Steal the Snorlax's Leftovers
|
||||
const instance = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: false });
|
||||
// Snorlax exp to Pokemon that did the stealing
|
||||
setEncounterExp(instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp);
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { generateModifierTypeOption, initBattleWithEnemyConfig, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierTypeOption,
|
||||
initBattleWithEnemyConfig,
|
||||
setEncounterExp,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
updatePlayerMoney,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
@ -23,140 +30,153 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
|||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import { getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
getEncounterPokemonLevelForWave,
|
||||
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounters/teleportingHijinks";
|
||||
|
||||
const MONEY_COST_MULTIPLIER = 1.75;
|
||||
const BIOME_CANDIDATES = [ Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND, Biome.WASTELAND, Biome.DOJO ];
|
||||
const MACHINE_INTERFACING_TYPES = [ PokemonType.ELECTRIC, PokemonType.STEEL ];
|
||||
const BIOME_CANDIDATES = [Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND, Biome.WASTELAND, Biome.DOJO];
|
||||
const MACHINE_INTERFACING_TYPES = [PokemonType.ELECTRIC, PokemonType.STEEL];
|
||||
|
||||
/**
|
||||
* Teleporting Hijinks encounter.
|
||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3817 | GitHub Issue #3817}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const TeleportingHijinksEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TELEPORTING_HIJINKS)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new WaveModulusRequirement([ 1, 2, 3 ], 10)) // Must be in first 3 waves after boss wave
|
||||
.withSceneRequirement(new MoneyRequirement(0, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withCatchAllowed(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "teleporting_hijinks_teleporter",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: 4,
|
||||
y: 4,
|
||||
yShadow: 1
|
||||
}
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const price = globalScene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER);
|
||||
encounter.setDialogueToken("price", price.toString());
|
||||
encounter.misc = {
|
||||
price
|
||||
};
|
||||
export const TeleportingHijinksEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.TELEPORTING_HIJINKS,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave
|
||||
.withSceneRequirement(new MoneyRequirement(0, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withCatchAllowed(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "teleporting_hijinks_teleporter",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: 4,
|
||||
y: 4,
|
||||
yShadow: 1,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const price = globalScene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER);
|
||||
encounter.setDialogueToken("price", price.toString());
|
||||
encounter.misc = {
|
||||
price,
|
||||
};
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
}
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Update money
|
||||
updatePlayerMoney(-globalScene.currentBattle.mysteryEncounter!.misc.price, true, false);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit();
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPokemonTypeRequirement(MACHINE_INTERFACING_TYPES, true, 1) // Must have Steel or Electric type
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
}
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit();
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
setEncounterExp(globalScene.currentBattle.mysteryEncounter!.selectedOption!.primaryPokemon!.id, 100);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Inspect the Machine
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Update money
|
||||
updatePlayerMoney(-globalScene.currentBattle.mysteryEncounter!.misc.price, true, false);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit();
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPokemonTypeRequirement(MACHINE_INTERFACING_TYPES, true, 1) // Must have Steel or Electric type
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit();
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
setEncounterExp(globalScene.currentBattle.mysteryEncounter!.selectedOption!.primaryPokemon!.id, 100);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Inspect the Machine
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Init enemy
|
||||
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
|
||||
const bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true);
|
||||
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
|
||||
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [{
|
||||
// Init enemy
|
||||
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
|
||||
const bossSpecies = globalScene.arena.randomSpecies(
|
||||
globalScene.currentBattle.waveIndex,
|
||||
level,
|
||||
0,
|
||||
getPartyLuckValue(globalScene.getPlayerParty()),
|
||||
true,
|
||||
);
|
||||
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
|
||||
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [
|
||||
{
|
||||
level: level,
|
||||
species: bossSpecies,
|
||||
dataSource: new PokemonData(bossPokemon),
|
||||
isBoss: true,
|
||||
}],
|
||||
};
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [ PokemonType.STEEL ])!;
|
||||
const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [ PokemonType.ELECTRIC ])!;
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: [ magnet, metalCoat ], fillRemaining: true });
|
||||
await transitionMysteryEncounterIntroVisuals(true, true);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
}
|
||||
)
|
||||
.build();
|
||||
const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.STEEL])!;
|
||||
const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.ELECTRIC])!;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [magnet, metalCoat],
|
||||
fillRemaining: true,
|
||||
});
|
||||
await transitionMysteryEncounterIntroVisuals(true, true);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
async function doBiomeTransitionDialogueAndBattleInit() {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
@ -167,34 +187,43 @@ async function doBiomeTransitionDialogueAndBattleInit() {
|
|||
|
||||
// Show dialogue and transition biome
|
||||
await showEncounterText(`${namespace}:transport`);
|
||||
await Promise.all([ animateBiomeChange(newBiome), transitionMysteryEncounterIntroVisuals() ]);
|
||||
await Promise.all([animateBiomeChange(newBiome), transitionMysteryEncounterIntroVisuals()]);
|
||||
globalScene.updateBiomeWaveText();
|
||||
globalScene.playBgm();
|
||||
await showEncounterText(`${namespace}:attacked`);
|
||||
|
||||
// Init enemy
|
||||
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
|
||||
const bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true);
|
||||
const bossSpecies = globalScene.arena.randomSpecies(
|
||||
globalScene.currentBattle.waveIndex,
|
||||
level,
|
||||
0,
|
||||
getPartyLuckValue(globalScene.getPlayerParty()),
|
||||
true,
|
||||
);
|
||||
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
|
||||
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
||||
|
||||
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
|
||||
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
|
||||
[ Stat.DEF, Stat.SPDEF, Stat.SPD ] :
|
||||
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
|
||||
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
|
||||
globalScene.currentBattle.waveIndex < 50
|
||||
? [Stat.DEF, Stat.SPDEF, Stat.SPD]
|
||||
: [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
|
||||
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [{
|
||||
level: level,
|
||||
species: bossSpecies,
|
||||
dataSource: new PokemonData(bossPokemon),
|
||||
isBoss: true,
|
||||
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:boss_enraged`);
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
|
||||
}
|
||||
}],
|
||||
pokemonConfigs: [
|
||||
{
|
||||
level: level,
|
||||
species: bossSpecies,
|
||||
dataSource: new PokemonData(bossPokemon),
|
||||
isBoss: true,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:boss_enraged`);
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return config;
|
||||
|
@ -203,7 +232,7 @@ async function doBiomeTransitionDialogueAndBattleInit() {
|
|||
async function animateBiomeChange(nextBiome: Biome) {
|
||||
return new Promise<void>(resolve => {
|
||||
globalScene.tweens.add({
|
||||
targets: [ globalScene.arenaEnemy, globalScene.lastEnemyTrainer ],
|
||||
targets: [globalScene.arenaEnemy, globalScene.lastEnemyTrainer],
|
||||
x: "+=300",
|
||||
duration: 2000,
|
||||
onComplete: () => {
|
||||
|
@ -219,10 +248,10 @@ async function animateBiomeChange(nextBiome: Biome) {
|
|||
globalScene.arenaPlayerTransition.setVisible(true);
|
||||
|
||||
globalScene.tweens.add({
|
||||
targets: [ globalScene.arenaPlayer, globalScene.arenaBgTransition, globalScene.arenaPlayerTransition ],
|
||||
targets: [globalScene.arenaPlayer, globalScene.arenaBgTransition, globalScene.arenaPlayerTransition],
|
||||
duration: 1000,
|
||||
ease: "Sine.easeInOut",
|
||||
alpha: (target: any) => target === globalScene.arenaPlayer ? 0 : 1,
|
||||
alpha: (target: any) => (target === globalScene.arenaPlayer ? 0 : 1),
|
||||
onComplete: () => {
|
||||
globalScene.arenaBg.setTexture(bgTexture);
|
||||
globalScene.arenaPlayer.setBiome(nextBiome);
|
||||
|
@ -242,9 +271,9 @@ async function animateBiomeChange(nextBiome: Biome) {
|
|||
targets: globalScene.arenaEnemy,
|
||||
x: "-=300",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
handleMysteryEncounterBattleFailed,
|
||||
initBattleWithEnemyConfig,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { trainerConfigs } from "#app/data/trainer-config";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
@ -39,7 +43,7 @@ const FINAL_STAGE_EVOLUTION_WAVE = 75;
|
|||
|
||||
const FRIENDSHIP_ADDED = 20;
|
||||
|
||||
class BreederSpeciesEvolution {
|
||||
class BreederSpeciesEvolution {
|
||||
species: Species;
|
||||
evolution: number;
|
||||
|
||||
|
@ -50,29 +54,65 @@ class BreederSpeciesEvolution {
|
|||
}
|
||||
|
||||
const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
|
||||
[ Species.MUNCHLAX, new BreederSpeciesEvolution(Species.SNORLAX, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.HAPPINY, new BreederSpeciesEvolution(Species.CHANSEY, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.BLISSEY, FINAL_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.MAGBY, new BreederSpeciesEvolution(Species.MAGMAR, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.MAGMORTAR, FINAL_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.ELEKID, new BreederSpeciesEvolution(Species.ELECTABUZZ, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ELECTIVIRE, FINAL_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.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.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.MUNCHLAX, new BreederSpeciesEvolution(Species.SNORLAX, SECOND_STAGE_EVOLUTION_WAVE)],
|
||||
[
|
||||
Species.HAPPINY,
|
||||
new BreederSpeciesEvolution(Species.CHANSEY, FIRST_STAGE_EVOLUTION_WAVE),
|
||||
new BreederSpeciesEvolution(Species.BLISSEY, FINAL_STAGE_EVOLUTION_WAVE),
|
||||
],
|
||||
[
|
||||
Species.MAGBY,
|
||||
new BreederSpeciesEvolution(Species.MAGMAR, FIRST_STAGE_EVOLUTION_WAVE),
|
||||
new BreederSpeciesEvolution(Species.MAGMORTAR, FINAL_STAGE_EVOLUTION_WAVE),
|
||||
],
|
||||
[
|
||||
Species.ELEKID,
|
||||
new BreederSpeciesEvolution(Species.ELECTABUZZ, FIRST_STAGE_EVOLUTION_WAVE),
|
||||
new BreederSpeciesEvolution(Species.ELECTIVIRE, FINAL_STAGE_EVOLUTION_WAVE),
|
||||
],
|
||||
[Species.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.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),
|
||||
],
|
||||
];
|
||||
|
||||
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.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.IGGLYBUFF, new BreederSpeciesEvolution(Species.JIGGLYPUFF, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.WIGGLYTUFF, FINAL_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.AZURILL, new BreederSpeciesEvolution(Species.MARILL, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.AZUMARILL, FINAL_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.WYNAUT, new BreederSpeciesEvolution(Species.WOBBUFFET, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.CHINGLING, new BreederSpeciesEvolution(Species.CHIMECHO, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.BONSLY, new BreederSpeciesEvolution(Species.SUDOWOODO, SECOND_STAGE_EVOLUTION_WAVE) ],
|
||||
[ Species.MANTYKE, new BreederSpeciesEvolution(Species.MANTINE, SECOND_STAGE_EVOLUTION_WAVE) ]
|
||||
[
|
||||
Species.PICHU,
|
||||
new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE),
|
||||
new BreederSpeciesEvolution(Species.RAICHU, FINAL_STAGE_EVOLUTION_WAVE),
|
||||
],
|
||||
[
|
||||
Species.PICHU,
|
||||
new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE),
|
||||
new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE),
|
||||
],
|
||||
[Species.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE)],
|
||||
[Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE)],
|
||||
[Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE)],
|
||||
[Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE)],
|
||||
[
|
||||
Species.IGGLYBUFF,
|
||||
new BreederSpeciesEvolution(Species.JIGGLYPUFF, FIRST_STAGE_EVOLUTION_WAVE),
|
||||
new BreederSpeciesEvolution(Species.WIGGLYTUFF, FINAL_STAGE_EVOLUTION_WAVE),
|
||||
],
|
||||
[
|
||||
Species.AZURILL,
|
||||
new BreederSpeciesEvolution(Species.MARILL, FIRST_STAGE_EVOLUTION_WAVE),
|
||||
new BreederSpeciesEvolution(Species.AZUMARILL, FINAL_STAGE_EVOLUTION_WAVE),
|
||||
],
|
||||
[Species.WYNAUT, new BreederSpeciesEvolution(Species.WOBBUFFET, SECOND_STAGE_EVOLUTION_WAVE)],
|
||||
[Species.CHINGLING, new BreederSpeciesEvolution(Species.CHIMECHO, SECOND_STAGE_EVOLUTION_WAVE)],
|
||||
[Species.BONSLY, new BreederSpeciesEvolution(Species.SUDOWOODO, SECOND_STAGE_EVOLUTION_WAVE)],
|
||||
[Species.MANTYKE, new BreederSpeciesEvolution(Species.MANTINE, SECOND_STAGE_EVOLUTION_WAVE)],
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -80,291 +120,344 @@ const POOL_2_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3818 | GitHub Issue #3818}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(4, 6, true) // Must have at least 4 legal pokemon in party
|
||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||
.withIntroDialogue([
|
||||
export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(4, 6, true) // Must have at least 4 legal pokemon in party
|
||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const waveIndex = globalScene.currentBattle.waveIndex;
|
||||
// Calculates what trainers are available for battle in the encounter
|
||||
|
||||
// If player is in space biome, uses special "Space" version of the trainer
|
||||
encounter.enemyPartyConfigs = [getPartyConfig()];
|
||||
|
||||
const cleffaSpecies =
|
||||
waveIndex < FIRST_STAGE_EVOLUTION_WAVE
|
||||
? Species.CLEFFA
|
||||
: waveIndex < FINAL_STAGE_EVOLUTION_WAVE
|
||||
? Species.CLEFAIRY
|
||||
: Species.CLEFABLE;
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
spriteKey: cleffaSpecies.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 14,
|
||||
y: -2,
|
||||
yShadow: -2,
|
||||
},
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
spriteKey: "expert_pokemon_breeder",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: -14,
|
||||
y: 4,
|
||||
yShadow: 2,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const waveIndex = globalScene.currentBattle.waveIndex;
|
||||
// Calculates what trainers are available for battle in the encounter
|
||||
];
|
||||
|
||||
// If player is in space biome, uses special "Space" version of the trainer
|
||||
encounter.enemyPartyConfigs = [
|
||||
getPartyConfig()
|
||||
];
|
||||
// Determine the 3 pokemon the player can battle with
|
||||
let partyCopy = globalScene.getPlayerParty().slice(0);
|
||||
partyCopy = partyCopy.filter(p => p.isAllowedInBattle()).sort((a, b) => a.friendship - b.friendship);
|
||||
|
||||
const cleffaSpecies = waveIndex < FIRST_STAGE_EVOLUTION_WAVE ? Species.CLEFFA : waveIndex < FINAL_STAGE_EVOLUTION_WAVE ? Species.CLEFAIRY : Species.CLEFABLE;
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: cleffaSpecies.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
x: 14,
|
||||
y: -2,
|
||||
yShadow: -2
|
||||
},
|
||||
{
|
||||
spriteKey: "expert_pokemon_breeder",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: -14,
|
||||
y: 4,
|
||||
yShadow: 2
|
||||
},
|
||||
];
|
||||
const pokemon1 = partyCopy[0];
|
||||
const pokemon2 = partyCopy[1];
|
||||
const pokemon3 = partyCopy[2];
|
||||
encounter.setDialogueToken("pokemon1Name", pokemon1.getNameToRender());
|
||||
encounter.setDialogueToken("pokemon2Name", pokemon2.getNameToRender());
|
||||
encounter.setDialogueToken("pokemon3Name", pokemon3.getNameToRender());
|
||||
|
||||
// Determine the 3 pokemon the player can battle with
|
||||
let partyCopy = globalScene.getPlayerParty().slice(0);
|
||||
partyCopy = partyCopy
|
||||
.filter(p => p.isAllowedInBattle())
|
||||
.sort((a, b) => a.friendship - b.friendship);
|
||||
// Dialogue and egg calcs for Pokemon 1
|
||||
const [pokemon1CommonEggs, pokemon1RareEggs] = calculateEggRewardsForPokemon(pokemon1);
|
||||
let pokemon1Tooltip = getEncounterText(`${namespace}:option.1.tooltip_base`)!;
|
||||
if (pokemon1RareEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, {
|
||||
count: pokemon1RareEggs,
|
||||
rarity: i18next.t("egg:greatTier"),
|
||||
});
|
||||
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon1RareEggs", eggsText);
|
||||
}
|
||||
if (pokemon1CommonEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, {
|
||||
count: pokemon1CommonEggs,
|
||||
rarity: i18next.t("egg:defaultTier"),
|
||||
});
|
||||
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon1CommonEggs", eggsText);
|
||||
}
|
||||
encounter.options[0].dialogue!.buttonTooltip = pokemon1Tooltip;
|
||||
|
||||
const pokemon1 = partyCopy[0];
|
||||
const pokemon2 = partyCopy[1];
|
||||
const pokemon3 = partyCopy[2];
|
||||
encounter.setDialogueToken("pokemon1Name", pokemon1.getNameToRender());
|
||||
encounter.setDialogueToken("pokemon2Name", pokemon2.getNameToRender());
|
||||
encounter.setDialogueToken("pokemon3Name", pokemon3.getNameToRender());
|
||||
// Dialogue and egg calcs for Pokemon 2
|
||||
const [pokemon2CommonEggs, pokemon2RareEggs] = calculateEggRewardsForPokemon(pokemon2);
|
||||
let pokemon2Tooltip = getEncounterText(`${namespace}:option.2.tooltip_base`)!;
|
||||
if (pokemon2RareEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, {
|
||||
count: pokemon2RareEggs,
|
||||
rarity: i18next.t("egg:greatTier"),
|
||||
});
|
||||
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon2RareEggs", eggsText);
|
||||
}
|
||||
if (pokemon2CommonEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, {
|
||||
count: pokemon2CommonEggs,
|
||||
rarity: i18next.t("egg:defaultTier"),
|
||||
});
|
||||
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon2CommonEggs", eggsText);
|
||||
}
|
||||
encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip;
|
||||
|
||||
// Dialogue and egg calcs for Pokemon 1
|
||||
const [ pokemon1CommonEggs, pokemon1RareEggs ] = calculateEggRewardsForPokemon(pokemon1);
|
||||
let pokemon1Tooltip = getEncounterText(`${namespace}:option.1.tooltip_base`)!;
|
||||
if (pokemon1RareEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1RareEggs, rarity: i18next.t("egg:greatTier") });
|
||||
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
|
||||
encounter.setDialogueToken("pokemon1RareEggs", eggsText);
|
||||
}
|
||||
if (pokemon1CommonEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1CommonEggs, rarity: i18next.t("egg:defaultTier") });
|
||||
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
|
||||
encounter.setDialogueToken("pokemon1CommonEggs", eggsText);
|
||||
}
|
||||
encounter.options[0].dialogue!.buttonTooltip = pokemon1Tooltip;
|
||||
// Dialogue and egg calcs for Pokemon 3
|
||||
const [pokemon3CommonEggs, pokemon3RareEggs] = calculateEggRewardsForPokemon(pokemon3);
|
||||
let pokemon3Tooltip = getEncounterText(`${namespace}:option.3.tooltip_base`)!;
|
||||
if (pokemon3RareEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, {
|
||||
count: pokemon3RareEggs,
|
||||
rarity: i18next.t("egg:greatTier"),
|
||||
});
|
||||
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon3RareEggs", eggsText);
|
||||
}
|
||||
if (pokemon3CommonEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, {
|
||||
count: pokemon3CommonEggs,
|
||||
rarity: i18next.t("egg:defaultTier"),
|
||||
});
|
||||
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
|
||||
eggs: eggsText,
|
||||
});
|
||||
encounter.setDialogueToken("pokemon3CommonEggs", eggsText);
|
||||
}
|
||||
encounter.options[2].dialogue!.buttonTooltip = pokemon3Tooltip;
|
||||
|
||||
// Dialogue and egg calcs for Pokemon 2
|
||||
const [ pokemon2CommonEggs, pokemon2RareEggs ] = calculateEggRewardsForPokemon(pokemon2);
|
||||
let pokemon2Tooltip = getEncounterText(`${namespace}:option.2.tooltip_base`)!;
|
||||
if (pokemon2RareEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2RareEggs, rarity: i18next.t("egg:greatTier") });
|
||||
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
|
||||
encounter.setDialogueToken("pokemon2RareEggs", eggsText);
|
||||
}
|
||||
if (pokemon2CommonEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") });
|
||||
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
|
||||
encounter.setDialogueToken("pokemon2CommonEggs", eggsText);
|
||||
}
|
||||
encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip;
|
||||
encounter.misc = {
|
||||
pokemon1,
|
||||
pokemon1CommonEggs,
|
||||
pokemon1RareEggs,
|
||||
pokemon2,
|
||||
pokemon2CommonEggs,
|
||||
pokemon2RareEggs,
|
||||
pokemon3,
|
||||
pokemon3CommonEggs,
|
||||
pokemon3RareEggs,
|
||||
};
|
||||
|
||||
// Dialogue and egg calcs for Pokemon 3
|
||||
const [ pokemon3CommonEggs, pokemon3RareEggs ] = calculateEggRewardsForPokemon(pokemon3);
|
||||
let pokemon3Tooltip = getEncounterText(`${namespace}:option.3.tooltip_base`)!;
|
||||
if (pokemon3RareEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3RareEggs, rarity: i18next.t("egg:greatTier") });
|
||||
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
|
||||
encounter.setDialogueToken("pokemon3RareEggs", eggsText);
|
||||
}
|
||||
if (pokemon3CommonEggs > 0) {
|
||||
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3CommonEggs, rarity: i18next.t("egg:defaultTier") });
|
||||
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
|
||||
encounter.setDialogueToken("pokemon3CommonEggs", eggsText);
|
||||
}
|
||||
encounter.options[2].dialogue!.buttonTooltip = pokemon3Tooltip;
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
selected: [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn battle with first pokemon
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
encounter.misc = {
|
||||
pokemon1,
|
||||
pokemon1CommonEggs,
|
||||
pokemon1RareEggs,
|
||||
pokemon2,
|
||||
pokemon2CommonEggs,
|
||||
pokemon2RareEggs,
|
||||
pokemon3,
|
||||
pokemon3CommonEggs,
|
||||
pokemon3RareEggs
|
||||
};
|
||||
const { pokemon1, pokemon1CommonEggs, pokemon1RareEggs } = encounter.misc;
|
||||
encounter.misc.chosenPokemon = pokemon1;
|
||||
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
|
||||
const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs);
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
|
||||
fillRemaining: true,
|
||||
},
|
||||
eggOptions,
|
||||
() => doPostEncounterCleanup(),
|
||||
);
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
selected: [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn battle with first pokemon
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
// Remove all Pokemon from the party except the chosen Pokemon
|
||||
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon1);
|
||||
|
||||
const { pokemon1, pokemon1CommonEggs, pokemon1RareEggs } = encounter.misc;
|
||||
encounter.misc.chosenPokemon = pokemon1;
|
||||
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
|
||||
const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs);
|
||||
setEncounterRewards(
|
||||
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
|
||||
eggOptions,
|
||||
() => doPostEncounterCleanup());
|
||||
// Configure outro dialogue for egg rewards
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
];
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon1CommonEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon1CommonEggs"],
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon1RareEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon1RareEggs"],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// Remove all Pokemon from the party except the chosen Pokemon
|
||||
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon1);
|
||||
encounter.onGameOver = onGameOver;
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
selected: [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn battle with second pokemon
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
// Configure outro dialogue for egg rewards
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
];
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon1CommonEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1CommonEggs"] }),
|
||||
});
|
||||
}
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon1RareEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1RareEggs"] }),
|
||||
});
|
||||
}
|
||||
const { pokemon2, pokemon2CommonEggs, pokemon2RareEggs } = encounter.misc;
|
||||
encounter.misc.chosenPokemon = pokemon2;
|
||||
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
|
||||
const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs);
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
|
||||
fillRemaining: true,
|
||||
},
|
||||
eggOptions,
|
||||
() => doPostEncounterCleanup(),
|
||||
);
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
selected: [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn battle with second pokemon
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
// Remove all Pokemon from the party except the chosen Pokemon
|
||||
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon2);
|
||||
|
||||
const { pokemon2, pokemon2CommonEggs, pokemon2RareEggs } = encounter.misc;
|
||||
encounter.misc.chosenPokemon = pokemon2;
|
||||
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
|
||||
const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs);
|
||||
setEncounterRewards(
|
||||
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
|
||||
eggOptions,
|
||||
() => doPostEncounterCleanup());
|
||||
// Configure outro dialogue for egg rewards
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
];
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon2CommonEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon2CommonEggs"],
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon2RareEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon2RareEggs"],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// Remove all Pokemon from the party except the chosen Pokemon
|
||||
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon2);
|
||||
encounter.onGameOver = onGameOver;
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
selected: [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn battle with third pokemon
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
// Configure outro dialogue for egg rewards
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
];
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon2CommonEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2CommonEggs"] }),
|
||||
});
|
||||
}
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon2RareEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2RareEggs"] }),
|
||||
});
|
||||
}
|
||||
const { pokemon3, pokemon3CommonEggs, pokemon3RareEggs } = encounter.misc;
|
||||
encounter.misc.chosenPokemon = pokemon3;
|
||||
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
|
||||
const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs);
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
|
||||
fillRemaining: true,
|
||||
},
|
||||
eggOptions,
|
||||
() => doPostEncounterCleanup(),
|
||||
);
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
selected: [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Spawn battle with third pokemon
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
// Remove all Pokemon from the party except the chosen Pokemon
|
||||
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon3);
|
||||
|
||||
const { pokemon3, pokemon3CommonEggs, pokemon3RareEggs } = encounter.misc;
|
||||
encounter.misc.chosenPokemon = pokemon3;
|
||||
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
|
||||
const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs);
|
||||
setEncounterRewards(
|
||||
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true },
|
||||
eggOptions,
|
||||
() => doPostEncounterCleanup());
|
||||
// Configure outro dialogue for egg rewards
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
];
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon3CommonEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon3CommonEggs"],
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon3RareEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, {
|
||||
numEggs: encounter.dialogueTokens["pokemon3RareEggs"],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// Remove all Pokemon from the party except the chosen Pokemon
|
||||
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon3);
|
||||
|
||||
// Configure outro dialogue for egg rewards
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
];
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon3CommonEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3CommonEggs"] }),
|
||||
});
|
||||
}
|
||||
if (encounter.dialogueTokens.hasOwnProperty("pokemon3RareEggs")) {
|
||||
encounter.dialogue.outro.push({
|
||||
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3RareEggs"] }),
|
||||
});
|
||||
}
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
encounter.onGameOver = onGameOver;
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
||||
function getPartyConfig(): EnemyPartyConfig {
|
||||
// Bug type superfan trainer config
|
||||
|
@ -373,64 +466,79 @@ function getPartyConfig(): EnemyPartyConfig {
|
|||
breederConfig.name = i18next.t(trainerNameKey);
|
||||
|
||||
// First mon is *always* this special cleffa
|
||||
const cleffaSpecies = waveIndex < FIRST_STAGE_EVOLUTION_WAVE ? Species.CLEFFA : waveIndex < FINAL_STAGE_EVOLUTION_WAVE ? Species.CLEFAIRY : Species.CLEFABLE;
|
||||
const cleffaSpecies =
|
||||
waveIndex < FIRST_STAGE_EVOLUTION_WAVE
|
||||
? Species.CLEFFA
|
||||
: waveIndex < FINAL_STAGE_EVOLUTION_WAVE
|
||||
? Species.CLEFAIRY
|
||||
: Species.CLEFABLE;
|
||||
const baseConfig: EnemyPartyConfig = {
|
||||
trainerType: TrainerType.EXPERT_POKEMON_BREEDER,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
nickname: i18next.t(`${namespace}:cleffa_1_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }),
|
||||
nickname: i18next.t(`${namespace}:cleffa_1_nickname`, {
|
||||
speciesName: getPokemonSpecies(cleffaSpecies).getName(),
|
||||
}),
|
||||
species: getPokemonSpecies(cleffaSpecies),
|
||||
isBoss: false,
|
||||
abilityIndex: 1, // Magic Guard
|
||||
shiny: false,
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [ Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH ],
|
||||
ivs: [ 31, 31, 31, 31, 31, 31 ],
|
||||
moveSet: [Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH],
|
||||
ivs: [31, 31, 31, 31, 31, 31],
|
||||
tera: PokemonType.STEEL,
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (globalScene.arena.biomeType === Biome.SPACE) {
|
||||
// All 3 members always Cleffa line, but different configs
|
||||
baseConfig.pokemonConfigs!.push({
|
||||
nickname: i18next.t(`${namespace}:cleffa_2_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }),
|
||||
species: getPokemonSpecies(cleffaSpecies),
|
||||
isBoss: false,
|
||||
abilityIndex: 1, // Magic Guard
|
||||
shiny: true,
|
||||
variant: 1,
|
||||
nature: Nature.MODEST,
|
||||
moveSet: [ Moves.MOONBLAST, Moves.MYSTICAL_FIRE, Moves.ICE_BEAM, Moves.THUNDERBOLT ],
|
||||
ivs: [ 31, 31, 31, 31, 31, 31 ]
|
||||
},
|
||||
{
|
||||
nickname: i18next.t(`${namespace}:cleffa_3_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }),
|
||||
species: getPokemonSpecies(cleffaSpecies),
|
||||
isBoss: false,
|
||||
abilityIndex: 2, // Friend Guard / Unaware
|
||||
shiny: true,
|
||||
variant: 2,
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [ Moves.TRI_ATTACK, Moves.STORED_POWER, Moves.TAKE_HEART, Moves.MOONLIGHT ],
|
||||
ivs: [ 31, 31, 31, 31, 31, 31 ]
|
||||
});
|
||||
baseConfig.pokemonConfigs!.push(
|
||||
{
|
||||
nickname: i18next.t(`${namespace}:cleffa_2_nickname`, {
|
||||
speciesName: getPokemonSpecies(cleffaSpecies).getName(),
|
||||
}),
|
||||
species: getPokemonSpecies(cleffaSpecies),
|
||||
isBoss: false,
|
||||
abilityIndex: 1, // Magic Guard
|
||||
shiny: true,
|
||||
variant: 1,
|
||||
nature: Nature.MODEST,
|
||||
moveSet: [Moves.MOONBLAST, Moves.MYSTICAL_FIRE, Moves.ICE_BEAM, Moves.THUNDERBOLT],
|
||||
ivs: [31, 31, 31, 31, 31, 31],
|
||||
},
|
||||
{
|
||||
nickname: i18next.t(`${namespace}:cleffa_3_nickname`, {
|
||||
speciesName: getPokemonSpecies(cleffaSpecies).getName(),
|
||||
}),
|
||||
species: getPokemonSpecies(cleffaSpecies),
|
||||
isBoss: false,
|
||||
abilityIndex: 2, // Friend Guard / Unaware
|
||||
shiny: true,
|
||||
variant: 2,
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [Moves.TRI_ATTACK, Moves.STORED_POWER, Moves.TAKE_HEART, Moves.MOONLIGHT],
|
||||
ivs: [31, 31, 31, 31, 31, 31],
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// Second member from pool 1
|
||||
const pool1Species = getSpeciesFromPool(POOL_1_POKEMON, waveIndex);
|
||||
// Third member from pool 2
|
||||
const pool2Species = getSpeciesFromPool(POOL_2_POKEMON, waveIndex);
|
||||
|
||||
baseConfig.pokemonConfigs!.push({
|
||||
species: getPokemonSpecies(pool1Species),
|
||||
isBoss: false,
|
||||
ivs: [ 31, 31, 31, 31, 31, 31 ]
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(pool2Species),
|
||||
isBoss: false,
|
||||
ivs: [ 31, 31, 31, 31, 31, 31 ]
|
||||
});
|
||||
baseConfig.pokemonConfigs!.push(
|
||||
{
|
||||
species: getPokemonSpecies(pool1Species),
|
||||
isBoss: false,
|
||||
ivs: [31, 31, 31, 31, 31, 31],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(pool2Species),
|
||||
isBoss: false,
|
||||
ivs: [31, 31, 31, 31, 31, 31],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return baseConfig;
|
||||
|
@ -471,7 +579,7 @@ function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number]
|
|||
// 1 Common egg for every point leftover
|
||||
numCommons += totalPoints % 4;
|
||||
|
||||
return [ numCommons, numRares ];
|
||||
return [numCommons, numRares];
|
||||
}
|
||||
|
||||
function getEggOptions(commonEggs: number, rareEggs: number) {
|
||||
|
@ -484,7 +592,7 @@ function getEggOptions(commonEggs: number, rareEggs: number) {
|
|||
pulled: false,
|
||||
sourceType: EggSourceType.EVENT,
|
||||
eggDescriptor: eggDescription,
|
||||
tier: EggTier.COMMON
|
||||
tier: EggTier.COMMON,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -494,7 +602,7 @@ function getEggOptions(commonEggs: number, rareEggs: number) {
|
|||
pulled: false,
|
||||
sourceType: EggSourceType.EVENT,
|
||||
eggDescriptor: eggDescription,
|
||||
tier: EggTier.RARE
|
||||
tier: EggTier.RARE,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -508,11 +616,8 @@ function removePokemonFromPartyAndStoreHeldItems(encounter: MysteryEncounter, ch
|
|||
party[chosenIndex] = party[0];
|
||||
party[0] = chosenPokemon;
|
||||
encounter.misc.originalParty = globalScene.getPlayerParty().slice(1);
|
||||
encounter.misc.originalPartyHeldItems = encounter.misc.originalParty
|
||||
.map(p => p.getHeldItems());
|
||||
globalScene["party"] = [
|
||||
chosenPokemon
|
||||
];
|
||||
encounter.misc.originalPartyHeldItems = encounter.misc.originalParty.map(p => p.getHeldItems());
|
||||
globalScene["party"] = [chosenPokemon];
|
||||
}
|
||||
|
||||
function restorePartyAndHeldItems() {
|
||||
|
@ -522,11 +627,11 @@ function restorePartyAndHeldItems() {
|
|||
|
||||
// Restore held items
|
||||
const originalHeldItems = encounter.misc.originalPartyHeldItems;
|
||||
originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => {
|
||||
pokemonHeldItemsList.forEach(heldItem => {
|
||||
for (const pokemonHeldItemsList of originalHeldItems) {
|
||||
for (const heldItem of pokemonHeldItemsList) {
|
||||
globalScene.addModifier(heldItem, true, false, false, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
}
|
||||
|
||||
|
@ -542,7 +647,7 @@ function onGameOver() {
|
|||
|
||||
// Restore original party, player loses all friendship with chosen mon (it remains fainted)
|
||||
restorePartyAndHeldItems();
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
chosenPokemon.friendship = 0;
|
||||
|
||||
// Clear all rewards that would have been earned
|
||||
|
@ -571,7 +676,7 @@ function onGameOver() {
|
|||
scale: 0.5,
|
||||
onComplete: () => {
|
||||
pokemon.leaveField(true, true, true);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -593,11 +698,10 @@ function onGameOver() {
|
|||
y: "+=16",
|
||||
alpha: 1,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 750
|
||||
duration: 750,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
handleMysteryEncounterBattleFailed(true);
|
||||
|
||||
return false;
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
leaveEncounterWithoutBattle,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
updatePlayerMoney,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { catchPokemon, getRandomSpeciesByStarterCost, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
catchPokemon,
|
||||
getRandomSpeciesByStarterCost,
|
||||
getSpriteKeysFromPokemon,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import type PokemonSpecies from "#app/data/pokemon-species";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { speciesStarterCosts } from "#app/data/balance/starters";
|
||||
|
@ -35,143 +43,149 @@ const SHINY_MAGIKARP_WEIGHT = 100;
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3799 | GitHub Issue #3799}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "pokemon_salesman",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true
|
||||
}
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.THE_POKEMON_SALESMAN,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "pokemon_salesman",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
speaker: `${namespace}:speaker`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
let species = getSalesmanSpeciesOffer();
|
||||
let tries = 0;
|
||||
let species = getSalesmanSpeciesOffer();
|
||||
let tries = 0;
|
||||
|
||||
// Reroll any species that don't have HAs
|
||||
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
|
||||
species = getSalesmanSpeciesOffer();
|
||||
tries++;
|
||||
}
|
||||
// Reroll any species that don't have HAs
|
||||
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
|
||||
species = getSalesmanSpeciesOffer();
|
||||
tries++;
|
||||
}
|
||||
|
||||
let pokemon: PlayerPokemon;
|
||||
if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) {
|
||||
// If no HA mon found or you roll 1%, give shiny Magikarp with random variant
|
||||
species = getPokemonSpecies(Species.MAGIKARP);
|
||||
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex, undefined, true);
|
||||
} else {
|
||||
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex);
|
||||
}
|
||||
pokemon.generateAndPopulateMoveset();
|
||||
let pokemon: PlayerPokemon;
|
||||
if (
|
||||
randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 ||
|
||||
isNullOrUndefined(species.abilityHidden) ||
|
||||
species.abilityHidden === Abilities.NONE
|
||||
) {
|
||||
// If no HA mon found or you roll 1%, give shiny Magikarp with random variant
|
||||
species = getPokemonSpecies(Species.MAGIKARP);
|
||||
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex, undefined, true);
|
||||
} else {
|
||||
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex);
|
||||
}
|
||||
pokemon.generateAndPopulateMoveset();
|
||||
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(pokemon);
|
||||
encounter.spriteConfigs.push({
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
isShiny: pokemon.shiny,
|
||||
variant: pokemon.variant
|
||||
});
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(pokemon);
|
||||
encounter.spriteConfigs.push({
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
isShiny: pokemon.shiny,
|
||||
variant: pokemon.variant,
|
||||
});
|
||||
|
||||
const starterTier = speciesStarterCosts[species.speciesId];
|
||||
// Prices decrease by starter tier less than 5, but only reduces cost by half at max
|
||||
let priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER * (Math.max(starterTier, 2.5) / 5);
|
||||
if (pokemon.shiny) {
|
||||
// Always max price for shiny (flip HA back to normal), and add special messaging
|
||||
priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER;
|
||||
pokemon.abilityIndex = 0;
|
||||
encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}:description_shiny`;
|
||||
encounter.options[0].dialogue!.buttonTooltip = `${namespace}:option.1.tooltip_shiny`;
|
||||
}
|
||||
const price = globalScene.getWaveMoneyAmount(priceMultiplier);
|
||||
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("price", price.toString());
|
||||
encounter.misc = {
|
||||
price: price,
|
||||
pokemon: pokemon
|
||||
};
|
||||
const starterTier = speciesStarterCosts[species.speciesId];
|
||||
// Prices decrease by starter tier less than 5, but only reduces cost by half at max
|
||||
let priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER * (Math.max(starterTier, 2.5) / 5);
|
||||
if (pokemon.shiny) {
|
||||
// Always max price for shiny (flip HA back to normal), and add special messaging
|
||||
priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER;
|
||||
pokemon.abilityIndex = 0;
|
||||
encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}:description_shiny`;
|
||||
encounter.options[0].dialogue!.buttonTooltip = `${namespace}:option.1.tooltip_shiny`;
|
||||
}
|
||||
const price = globalScene.getWaveMoneyAmount(priceMultiplier);
|
||||
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
|
||||
encounter.setDialogueToken("price", price.toString());
|
||||
encounter.misc = {
|
||||
price: price,
|
||||
pokemon: pokemon,
|
||||
};
|
||||
|
||||
pokemon.calculateStats();
|
||||
pokemon.calculateStats();
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected_message`,
|
||||
}
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const price = encounter.misc.price;
|
||||
const purchasedPokemon = encounter.misc.pokemon as PlayerPokemon;
|
||||
|
||||
// Update money
|
||||
updatePlayerMoney(-price, true, false);
|
||||
|
||||
// Show dialogue
|
||||
await showEncounterDialogue(`${namespace}:option.1.selected_dialogue`, `${namespace}:speaker`);
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
|
||||
// "Catch" purchased pokemon
|
||||
const data = new PokemonData(purchasedPokemon);
|
||||
data.player = false;
|
||||
await catchPokemon(data.toPokemon() as EnemyPokemon, null, PokeballType.POKEBALL, true, true);
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
text: `${namespace}:option.1.selected_message`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const price = encounter.misc.price;
|
||||
const purchasedPokemon = encounter.misc.pokemon as PlayerPokemon;
|
||||
|
||||
// Update money
|
||||
updatePlayerMoney(-price, true, false);
|
||||
|
||||
// Show dialogue
|
||||
await showEncounterDialogue(`${namespace}:option.1.selected_dialogue`, `${namespace}:speaker`);
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
|
||||
// "Catch" purchased pokemon
|
||||
const data = new PokemonData(purchasedPokemon);
|
||||
data.player = false;
|
||||
await catchPokemon(data.toPokemon() as EnemyPokemon, null, PokeballType.POKEBALL, true, true);
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
|
||||
*/
|
||||
export function getSalesmanSpeciesOffer(): PokemonSpecies {
|
||||
return getPokemonSpecies(getRandomSpeciesByStarterCost([ 0, 5 ], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false));
|
||||
return getPokemonSpecies(
|
||||
getRandomSpeciesByStarterCost([0, 5], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
initBattleWithEnemyConfig,
|
||||
loadCustomMovesForEncounter,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
generateModifierType,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
@ -35,179 +42,188 @@ const BST_INCREASE_VALUE = 10;
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3803 | GitHub Issue #3803}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const TheStrongStuffEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
|
||||
.withMaxAllowedEncounters(1)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "berry_juice",
|
||||
fileRoot: "items",
|
||||
hasShadow: true,
|
||||
isItem: true,
|
||||
scale: 1.25,
|
||||
x: -15,
|
||||
y: 3,
|
||||
disableAnimation: true
|
||||
},
|
||||
{
|
||||
spriteKey: Species.SHUCKLE.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
scale: 1.25,
|
||||
x: 20,
|
||||
y: 10,
|
||||
yShadow: 7
|
||||
},
|
||||
]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.THE_STRONG_STUFF,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
|
||||
.withMaxAllowedEncounters(1)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "berry_juice",
|
||||
fileRoot: "items",
|
||||
hasShadow: true,
|
||||
isItem: true,
|
||||
scale: 1.25,
|
||||
x: -15,
|
||||
y: 3,
|
||||
disableAnimation: true,
|
||||
},
|
||||
{
|
||||
spriteKey: Species.SHUCKLE.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
scale: 1.25,
|
||||
x: 20,
|
||||
y: 10,
|
||||
yShadow: 7,
|
||||
},
|
||||
]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Calculate boss mon
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveModifier: 1,
|
||||
disableSwitch: true,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.SHUCKLE),
|
||||
isBoss: true,
|
||||
bossSegments: 5,
|
||||
shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked
|
||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
],
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.2.stat_boost`);
|
||||
globalScene.unshiftPhase(
|
||||
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.DEF, Stat.SPDEF], 2),
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
loadCustomMovesForEncounter([Moves.GASTRO_ACID, Moves.STEALTH_ROCK]);
|
||||
|
||||
encounter.setDialogueToken("shuckleName", getPokemonSpecies(Species.SHUCKLE).getName());
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Do blackout and hide intro visuals during blackout
|
||||
globalScene.time.delayedCall(750, () => {
|
||||
transitionMysteryEncounterIntroVisuals(true, true, 50);
|
||||
});
|
||||
|
||||
// Calculate boss mon
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveModifier: 1,
|
||||
disableSwitch: true,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.SHUCKLE),
|
||||
isBoss: true,
|
||||
bossSegments: 5,
|
||||
shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked
|
||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2
|
||||
}
|
||||
],
|
||||
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.2.stat_boost`);
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.DEF, Stat.SPDEF ], 2));
|
||||
}
|
||||
}
|
||||
],
|
||||
};
|
||||
// -15 to all base stats of highest BST (halved for HP), +10 to all base stats of rest of party (halved for HP)
|
||||
// Sort party by bst
|
||||
const sortedParty = globalScene
|
||||
.getPlayerParty()
|
||||
.slice(0)
|
||||
.sort((pokemon1, pokemon2) => {
|
||||
const pokemon1Bst = pokemon1.getSpeciesForm().getBaseStatTotal();
|
||||
const pokemon2Bst = pokemon2.getSpeciesForm().getBaseStatTotal();
|
||||
return pokemon2Bst - pokemon1Bst;
|
||||
});
|
||||
|
||||
encounter.enemyPartyConfigs = [ config ];
|
||||
sortedParty.forEach((pokemon, index) => {
|
||||
if (index < 2) {
|
||||
// -15 to the two highest BST mons
|
||||
modifyPlayerPokemonBST(pokemon, -HIGH_BST_REDUCTION_VALUE);
|
||||
encounter.setDialogueToken("highBstPokemon" + (index + 1), pokemon.getNameToRender());
|
||||
} else {
|
||||
// +10 for the rest
|
||||
modifyPlayerPokemonBST(pokemon, BST_INCREASE_VALUE);
|
||||
}
|
||||
});
|
||||
|
||||
loadCustomMovesForEncounter([ Moves.GASTRO_ACID, Moves.STEALTH_ROCK ]);
|
||||
|
||||
encounter.setDialogueToken("shuckleName", getPokemonSpecies(Species.SHUCKLE).getName());
|
||||
encounter.setDialogueToken("reductionValue", HIGH_BST_REDUCTION_VALUE.toString());
|
||||
encounter.setDialogueToken("increaseValue", BST_INCREASE_VALUE.toString());
|
||||
await showEncounterText(`${namespace}:option.1.selected_2`, null, undefined, true);
|
||||
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
];
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`
|
||||
}
|
||||
]
|
||||
},
|
||||
async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
// Do blackout and hide intro visuals during blackout
|
||||
globalScene.time.delayedCall(750, () => {
|
||||
transitionMysteryEncounterIntroVisuals(true, true, 50);
|
||||
});
|
||||
},
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW],
|
||||
fillRemaining: true,
|
||||
});
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.GASTRO_ACID),
|
||||
ignorePp: true,
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.STEALTH_ROCK),
|
||||
ignorePp: true,
|
||||
},
|
||||
);
|
||||
|
||||
// -15 to all base stats of highest BST (halved for HP), +10 to all base stats of rest of party (halved for HP)
|
||||
// Sort party by bst
|
||||
const sortedParty = globalScene.getPlayerParty().slice(0)
|
||||
.sort((pokemon1, pokemon2) => {
|
||||
const pokemon1Bst = pokemon1.getSpeciesForm().getBaseStatTotal();
|
||||
const pokemon2Bst = pokemon2.getSpeciesForm().getBaseStatTotal();
|
||||
return pokemon2Bst - pokemon1Bst;
|
||||
});
|
||||
|
||||
sortedParty.forEach((pokemon, index) => {
|
||||
if (index < 2) {
|
||||
// -15 to the two highest BST mons
|
||||
modifyPlayerPokemonBST(pokemon, -HIGH_BST_REDUCTION_VALUE);
|
||||
encounter.setDialogueToken("highBstPokemon" + (index + 1), pokemon.getNameToRender());
|
||||
} else {
|
||||
// +10 for the rest
|
||||
modifyPlayerPokemonBST(pokemon, BST_INCREASE_VALUE);
|
||||
}
|
||||
});
|
||||
|
||||
encounter.setDialogueToken("reductionValue", HIGH_BST_REDUCTION_VALUE.toString());
|
||||
encounter.setDialogueToken("increaseValue", BST_INCREASE_VALUE.toString());
|
||||
await showEncounterText(`${namespace}:option.1.selected_2`, null, undefined, true);
|
||||
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
}
|
||||
];
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SOUL_DEW ], fillRemaining: true });
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [ BattlerIndex.PLAYER ],
|
||||
move: new PokemonMove(Moves.GASTRO_ACID),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [ BattlerIndex.PLAYER ],
|
||||
move: new PokemonMove(Moves.STEALTH_ROCK),
|
||||
ignorePp: true
|
||||
});
|
||||
|
||||
encounter.dialogue.outro = [];
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.build();
|
||||
encounter.dialogue.outro = [];
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
generateModifierTypeOption,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
@ -36,111 +43,115 @@ const namespace = "mysteryEncounters/theWinstrateChallenge";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3821 | GitHub Issue #3821}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const TheWinstrateChallengeEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_WINSTRATE_CHALLENGE)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "vito",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: false,
|
||||
x: 16,
|
||||
y: -4
|
||||
},
|
||||
{
|
||||
spriteKey: "vivi",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: false,
|
||||
x: -14,
|
||||
y: -4
|
||||
},
|
||||
{
|
||||
spriteKey: "victor",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: -32
|
||||
},
|
||||
{
|
||||
spriteKey: "victoria",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 40,
|
||||
},
|
||||
{
|
||||
spriteKey: "vicky",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 3,
|
||||
y: 5,
|
||||
yShadow: 5
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.THE_WINSTRATE_CHALLENGE,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "vito",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: false,
|
||||
x: 16,
|
||||
y: -4,
|
||||
},
|
||||
{
|
||||
spriteKey: "vivi",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: false,
|
||||
x: -14,
|
||||
y: -4,
|
||||
},
|
||||
{
|
||||
spriteKey: "victor",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: -32,
|
||||
},
|
||||
{
|
||||
spriteKey: "victoria",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 40,
|
||||
},
|
||||
{
|
||||
spriteKey: "vicky",
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 3,
|
||||
y: 5,
|
||||
yShadow: 5,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Loaded back to front for pop() operations
|
||||
encounter.enemyPartyConfigs.push(getVitoTrainerConfig());
|
||||
encounter.enemyPartyConfigs.push(getVickyTrainerConfig());
|
||||
encounter.enemyPartyConfigs.push(getViviTrainerConfig());
|
||||
encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig());
|
||||
encounter.enemyPartyConfigs.push(getVictorTrainerConfig());
|
||||
// Loaded back to front for pop() operations
|
||||
encounter.enemyPartyConfigs.push(getVitoTrainerConfig());
|
||||
encounter.enemyPartyConfigs.push(getVickyTrainerConfig());
|
||||
encounter.enemyPartyConfigs.push(getViviTrainerConfig());
|
||||
encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig());
|
||||
encounter.enemyPartyConfigs.push(getVictorTrainerConfig());
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Spawn 5 trainer battles back to back with Macho Brace in rewards
|
||||
globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = async () => {
|
||||
await endTrainerBattleAndShowDialogue();
|
||||
};
|
||||
await transitionMysteryEncounterIntroVisuals(true, false);
|
||||
await spawnNextTrainerOrEndEncounter();
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Refuse the challenge, they full heal the party and give the player a Rarer Candy
|
||||
globalScene.unshiftPhase(new PartyHealPhase(true));
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.RARER_CANDY ], fillRemaining: false });
|
||||
leaveEncounterWithoutBattle();
|
||||
}
|
||||
)
|
||||
.build();
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Spawn 5 trainer battles back to back with Macho Brace in rewards
|
||||
globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = async () => {
|
||||
await endTrainerBattleAndShowDialogue();
|
||||
};
|
||||
await transitionMysteryEncounterIntroVisuals(true, false);
|
||||
await spawnNextTrainerOrEndEncounter();
|
||||
},
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Refuse the challenge, they full heal the party and give the player a Rarer Candy
|
||||
globalScene.unshiftPhase(new PartyHealPhase(true));
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY],
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle();
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
async function spawnNextTrainerOrEndEncounter() {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
@ -159,7 +170,10 @@ async function spawnNextTrainerOrEndEncounter() {
|
|||
globalScene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in
|
||||
const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!;
|
||||
machoBrace.type.tier = ModifierTier.MASTER;
|
||||
setEncounterRewards({ guaranteedModifierTypeOptions: [ machoBrace ], fillRemaining: false });
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeOptions: [machoBrace],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.doContinueEncounter = undefined;
|
||||
leaveEncounterWithoutBattle(false, MysteryEncounterMode.NO_BATTLE);
|
||||
} else {
|
||||
|
@ -168,6 +182,7 @@ async function spawnNextTrainerOrEndEncounter() {
|
|||
}
|
||||
|
||||
function endTrainerBattleAndShowDialogue(): Promise<void> {
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
|
||||
return new Promise(async resolve => {
|
||||
if (globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs.length === 0) {
|
||||
// Battle is over
|
||||
|
@ -182,7 +197,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
|
|||
duration: 750,
|
||||
onComplete: () => {
|
||||
globalScene.field.remove(trainer, true);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -191,13 +206,19 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
|
|||
} else {
|
||||
globalScene.arena.resetArenaEffects();
|
||||
const playerField = globalScene.getPlayerField();
|
||||
playerField.forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED));
|
||||
for (const pokemon of playerField) {
|
||||
pokemon.lapseTag(BattlerTagType.COMMANDED);
|
||||
}
|
||||
playerField.forEach((_, p) => globalScene.unshiftPhase(new ReturnPhase(p)));
|
||||
|
||||
for (const pokemon of globalScene.getPlayerParty()) {
|
||||
// Only trigger form change when Eiscue is in Noice form
|
||||
// Hardcoded Eiscue for now in case it is fused with another pokemon
|
||||
if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) {
|
||||
if (
|
||||
pokemon.species.speciesId === Species.EISCUE &&
|
||||
pokemon.hasAbility(Abilities.ICE_FACE) &&
|
||||
pokemon.formIndex === 1
|
||||
) {
|
||||
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger);
|
||||
}
|
||||
|
||||
|
@ -222,7 +243,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
|
|||
onComplete: () => {
|
||||
globalScene.field.remove(trainer, true);
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -238,38 +259,38 @@ function getVictorTrainerConfig(): EnemyPartyConfig {
|
|||
isBoss: false,
|
||||
abilityIndex: 0, // Guts
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [ Moves.FACADE, Moves.BRAVE_BIRD, Moves.PROTECT, Moves.QUICK_ATTACK ],
|
||||
moveSet: [Moves.FACADE, Moves.BRAVE_BIRD, Moves.PROTECT, Moves.QUICK_ATTACK],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
isTransferable: false,
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.OBSTAGOON),
|
||||
isBoss: false,
|
||||
abilityIndex: 1, // Guts
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [ Moves.FACADE, Moves.OBSTRUCT, Moves.NIGHT_SLASH, Moves.FIRE_PUNCH ],
|
||||
moveSet: [Moves.FACADE, Moves.OBSTRUCT, Moves.NIGHT_SLASH, Moves.FIRE_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -282,39 +303,43 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig {
|
|||
isBoss: false,
|
||||
abilityIndex: 0, // Natural Cure
|
||||
nature: Nature.CALM,
|
||||
moveSet: [ Moves.SYNTHESIS, Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.SLEEP_POWDER ],
|
||||
moveSet: [Moves.SYNTHESIS, Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.SLEEP_POWDER],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType,
|
||||
isTransferable: false
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.GARDEVOIR),
|
||||
isBoss: false,
|
||||
formIndex: 1,
|
||||
nature: Nature.TIMID,
|
||||
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: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ PokemonType.PSYCHIC ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
|
||||
PokemonType.PSYCHIC,
|
||||
]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ PokemonType.FAIRY ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
|
||||
PokemonType.FAIRY,
|
||||
]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -327,53 +352,53 @@ function getViviTrainerConfig(): EnemyPartyConfig {
|
|||
isBoss: false,
|
||||
abilityIndex: 3, // Lightning Rod
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [ Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST ],
|
||||
moveSet: [Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
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,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.BRELOOM),
|
||||
isBoss: false,
|
||||
abilityIndex: 1, // Poison Heal
|
||||
nature: Nature.JOLLY,
|
||||
moveSet: [ Moves.SPORE, Moves.SWORDS_DANCE, Moves.SEED_BOMB, Moves.DRAIN_PUNCH ],
|
||||
moveSet: [Moves.SPORE, Moves.SWORDS_DANCE, Moves.SEED_BOMB, Moves.DRAIN_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [ Stat.HP ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
|
||||
stackCount: 4,
|
||||
isTransferable: false
|
||||
isTransferable: false,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.CAMERUPT),
|
||||
isBoss: false,
|
||||
formIndex: 1,
|
||||
nature: Nature.CALM,
|
||||
moveSet: [ Moves.EARTH_POWER, Moves.FIRE_BLAST, Moves.YAWN, Moves.PROTECT ],
|
||||
moveSet: [Moves.EARTH_POWER, Moves.FIRE_BLAST, Moves.YAWN, Moves.PROTECT],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 3,
|
||||
isTransferable: false
|
||||
isTransferable: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -386,15 +411,15 @@ function getVickyTrainerConfig(): EnemyPartyConfig {
|
|||
isBoss: false,
|
||||
formIndex: 1,
|
||||
nature: Nature.IMPISH,
|
||||
moveSet: [ Moves.AXE_KICK, Moves.ICE_PUNCH, Moves.ZEN_HEADBUTT, Moves.BULLET_PUNCH ],
|
||||
moveSet: [Moves.AXE_KICK, Moves.ICE_PUNCH, Moves.ZEN_HEADBUTT, Moves.BULLET_PUNCH],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -407,110 +432,110 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
|
|||
isBoss: false,
|
||||
abilityIndex: 0, // Soundproof
|
||||
nature: Nature.MODEST,
|
||||
moveSet: [ Moves.THUNDERBOLT, Moves.GIGA_DRAIN, Moves.FOUL_PLAY, Moves.THUNDER_WAVE ],
|
||||
moveSet: [Moves.THUNDERBOLT, Moves.GIGA_DRAIN, Moves.FOUL_PLAY, Moves.THUNDER_WAVE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [ Stat.SPD ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.SWALOT),
|
||||
isBoss: false,
|
||||
abilityIndex: 2, // Gluttony
|
||||
nature: Nature.QUIET,
|
||||
moveSet: [ Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.ICE_BEAM, Moves.EARTHQUAKE ],
|
||||
moveSet: [Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.ICE_BEAM, Moves.EARTHQUAKE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.STARF ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.STARF]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SALAC ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SALAC]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LANSAT ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LANSAT]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LIECHI ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LIECHI]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.PETAYA ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.PETAYA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LEPPA ]) as PokemonHeldItemModifierType,
|
||||
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.DODRIO),
|
||||
isBoss: false,
|
||||
abilityIndex: 2, // Tangled Feet
|
||||
nature: Nature.JOLLY,
|
||||
moveSet: [ Moves.DRILL_PECK, Moves.QUICK_ATTACK, Moves.THRASH, Moves.KNOCK_OFF ],
|
||||
moveSet: [Moves.DRILL_PECK, Moves.QUICK_ATTACK, Moves.THRASH, Moves.KNOCK_OFF],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
}
|
||||
]
|
||||
isTransferable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.ALAKAZAM),
|
||||
isBoss: false,
|
||||
formIndex: 1,
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [ Moves.PSYCHIC, Moves.SHADOW_BALL, Moves.FOCUS_BLAST, Moves.THUNDERBOLT ],
|
||||
moveSet: [Moves.PSYCHIC, Moves.SHADOW_BALL, Moves.FOCUS_BLAST, Moves.THUNDERBOLT],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
isTransferable: false,
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
species: getPokemonSpecies(Species.DARMANITAN),
|
||||
isBoss: false,
|
||||
abilityIndex: 0, // Sheer Force
|
||||
nature: Nature.IMPISH,
|
||||
moveSet: [ Moves.EARTHQUAKE, Moves.U_TURN, Moves.FLARE_BLITZ, Moves.ROCK_SLIDE ],
|
||||
moveSet: [Moves.EARTHQUAKE, Moves.U_TURN, Moves.FLARE_BLITZ, Moves.ROCK_SLIDE],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
|
||||
stackCount: 2,
|
||||
isTransferable: false
|
||||
isTransferable: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import type { Ability } from "#app/data/ability";
|
||||
import { allAbilities } from "#app/data/ability";
|
||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectPokemonForOption,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getNatureName } from "#app/data/nature";
|
||||
import { speciesStarterCosts } from "#app/data/balance/starters";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
|
@ -35,360 +40,350 @@ const namespace = "mysteryEncounters/trainingSession";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3802 | GitHub Issue #3802}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const TrainingSessionEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRAINING_SESSION)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party
|
||||
.withFleeAllowed(false)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withPreventGameStatsUpdates(true) // Do not count the Pokemon as seen or defeated since it is ours
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "training_session_gear",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 6,
|
||||
x: 5,
|
||||
yShadow: -2
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
encounter.misc = {
|
||||
playerPokemon: pokemon,
|
||||
export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.TRAINING_SESSION,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party
|
||||
.withFleeAllowed(false)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withPreventGameStatsUpdates(true) // Do not count the Pokemon as seen or defeated since it is ours
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "training_session_gear",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 6,
|
||||
x: 5,
|
||||
yShadow: -2,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
encounter.misc = {
|
||||
playerPokemon: pokemon,
|
||||
};
|
||||
};
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
|
||||
|
||||
// Spawn light training session with chosen pokemon
|
||||
// Every 50 waves, add +1 boss segment, capping at 5
|
||||
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 50), 5);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
encounter.setDialogueToken("stat1", "-");
|
||||
encounter.setDialogueToken("stat2", "-");
|
||||
// Add the pokemon back to party with IV boost
|
||||
let ivIndexes: any[] = [];
|
||||
playerPokemon.ivs.forEach((iv, index) => {
|
||||
if (iv < 31) {
|
||||
ivIndexes.push({ iv: iv, index: index });
|
||||
}
|
||||
});
|
||||
|
||||
// Improves 2 random non-maxed IVs
|
||||
// +10 if IV is < 10, +5 if between 10-20, and +3 if > 20
|
||||
// A 0-4 starting IV will cap in 6 encounters (assuming you always rolled that IV)
|
||||
// 5-14 starting IV caps in 5 encounters
|
||||
// 15-19 starting IV caps in 4 encounters
|
||||
// 20-24 starting IV caps in 3 encounters
|
||||
// 25-27 starting IV caps in 2 encounters
|
||||
let improvedCount = 0;
|
||||
while (ivIndexes.length > 0 && improvedCount < 2) {
|
||||
ivIndexes = randSeedShuffle(ivIndexes);
|
||||
const ivToChange = ivIndexes.pop();
|
||||
let newVal = ivToChange.iv;
|
||||
if (improvedCount === 0) {
|
||||
encounter.setDialogueToken("stat1", i18next.t(getStatKey(ivToChange.index)) ?? "");
|
||||
} else {
|
||||
encounter.setDialogueToken("stat2", i18next.t(getStatKey(ivToChange.index)) ?? "");
|
||||
}
|
||||
|
||||
// Corrects required encounter breakpoints to be continuous for all IV values
|
||||
if (ivToChange.iv <= 21 && ivToChange.iv - (1 % 5) === 0) {
|
||||
newVal += 1;
|
||||
}
|
||||
|
||||
newVal += ivToChange.iv <= 10 ? 10 : ivToChange.iv <= 20 ? 5 : 3;
|
||||
newVal = Math.min(newVal, 31);
|
||||
playerPokemon.ivs[ivToChange.index] = newVal;
|
||||
improvedCount++;
|
||||
}
|
||||
|
||||
if (improvedCount > 0) {
|
||||
playerPokemon.calculateStats();
|
||||
globalScene.gameData.updateSpeciesDexIvs(playerPokemon.species.getRootSpeciesId(true), playerPokemon.ivs);
|
||||
globalScene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
}
|
||||
|
||||
// Add pokemon and mods back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
queueEncounterMessage(`${namespace}:option.1.finished`);
|
||||
};
|
||||
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.2.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
// Open menu for selecting pokemon and Nature
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const natures = new Array(25).fill(null).map((_val, i) => i as Nature);
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for nature selection
|
||||
return natures.map((nature: Nature) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: getNatureName(nature, true, true, true, globalScene.uiTheme),
|
||||
handler: () => {
|
||||
// Pokemon and second option selected
|
||||
encounter.setDialogueToken("nature", getNatureName(nature));
|
||||
encounter.misc = {
|
||||
playerPokemon: pokemon,
|
||||
chosenNature: nature,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
};
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
|
||||
|
||||
// Spawn light training session with chosen pokemon
|
||||
// Every 50 waves, add +1 boss segment, capping at 5
|
||||
const segments = Math.min(
|
||||
2 + Math.floor(globalScene.currentBattle.waveIndex / 50),
|
||||
5
|
||||
);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
// Spawn medium training session with chosen pokemon
|
||||
// Every 40 waves, add +1 boss segment, capping at 6
|
||||
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 40), 6);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
encounter.setDialogueToken("stat1", "-");
|
||||
encounter.setDialogueToken("stat2", "-");
|
||||
// Add the pokemon back to party with IV boost
|
||||
let ivIndexes: any[] = [];
|
||||
playerPokemon.ivs.forEach((iv, index) => {
|
||||
if (iv < 31) {
|
||||
ivIndexes.push({ iv: iv, index: index });
|
||||
}
|
||||
});
|
||||
const onBeforeRewardsPhase = () => {
|
||||
queueEncounterMessage(`${namespace}:option.2.finished`);
|
||||
// Add the pokemon back to party with Nature change
|
||||
playerPokemon.setCustomNature(encounter.misc.chosenNature);
|
||||
globalScene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature);
|
||||
|
||||
// Improves 2 random non-maxed IVs
|
||||
// +10 if IV is < 10, +5 if between 10-20, and +3 if > 20
|
||||
// A 0-4 starting IV will cap in 6 encounters (assuming you always rolled that IV)
|
||||
// 5-14 starting IV caps in 5 encounters
|
||||
// 15-19 starting IV caps in 4 encounters
|
||||
// 20-24 starting IV caps in 3 encounters
|
||||
// 25-27 starting IV caps in 2 encounters
|
||||
let improvedCount = 0;
|
||||
while (ivIndexes.length > 0 && improvedCount < 2) {
|
||||
ivIndexes = randSeedShuffle(ivIndexes);
|
||||
const ivToChange = ivIndexes.pop();
|
||||
let newVal = ivToChange.iv;
|
||||
if (improvedCount === 0) {
|
||||
encounter.setDialogueToken(
|
||||
"stat1",
|
||||
i18next.t(getStatKey(ivToChange.index)) ?? ""
|
||||
);
|
||||
} else {
|
||||
encounter.setDialogueToken(
|
||||
"stat2",
|
||||
i18next.t(getStatKey(ivToChange.index)) ?? ""
|
||||
);
|
||||
}
|
||||
// Add pokemon and modifiers back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
};
|
||||
|
||||
// Corrects required encounter breakpoints to be continuous for all IV values
|
||||
if (ivToChange.iv <= 21 && ivToChange.iv - (1 % 5) === 0) {
|
||||
newVal += 1;
|
||||
}
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
|
||||
newVal += ivToChange.iv <= 10 ? 10 : ivToChange.iv <= 20 ? 5 : 3;
|
||||
newVal = Math.min(newVal, 31);
|
||||
playerPokemon.ivs[ivToChange.index] = newVal;
|
||||
improvedCount++;
|
||||
}
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
// Open menu for selecting pokemon and ability to learn
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for ability selection
|
||||
const speciesForm = pokemon.getFusionSpeciesForm()
|
||||
? pokemon.getFusionSpeciesForm()
|
||||
: pokemon.getSpeciesForm();
|
||||
const abilityCount = speciesForm.getAbilityCount();
|
||||
const abilities: Ability[] = new Array(abilityCount)
|
||||
.fill(null)
|
||||
.map((_val, i) => allAbilities[speciesForm.getAbility(i)]);
|
||||
|
||||
if (improvedCount > 0) {
|
||||
playerPokemon.calculateStats();
|
||||
globalScene.gameData.updateSpeciesDexIvs(playerPokemon.species.getRootSpeciesId(true), playerPokemon.ivs);
|
||||
globalScene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
}
|
||||
|
||||
// Add pokemon and mods back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
queueEncounterMessage(`${namespace}:option.1.finished`);
|
||||
};
|
||||
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.2.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
// Open menu for selecting pokemon and Nature
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const natures = new Array(25).fill(null).map((val, i) => i as Nature);
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for nature selection
|
||||
return natures.map((nature: Nature) => {
|
||||
const optionSelectItems: OptionSelectItem[] = [];
|
||||
abilities.forEach((ability: Ability, index) => {
|
||||
if (!optionSelectItems.some(o => o.label === ability.name)) {
|
||||
const option: OptionSelectItem = {
|
||||
label: getNatureName(nature, true, true, true, globalScene.uiTheme),
|
||||
label: ability.name,
|
||||
handler: () => {
|
||||
// Pokemon and second option selected
|
||||
encounter.setDialogueToken("nature", getNatureName(nature));
|
||||
// Pokemon and ability selected
|
||||
encounter.setDialogueToken("ability", ability.name);
|
||||
encounter.misc = {
|
||||
playerPokemon: pokemon,
|
||||
chosenNature: nature,
|
||||
abilityIndex: index,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
showEncounterText(ability.description, 0, 0, false);
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
|
||||
|
||||
// Spawn medium training session with chosen pokemon
|
||||
// Every 40 waves, add +1 boss segment, capping at 6
|
||||
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 40), 6);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
queueEncounterMessage(`${namespace}:option.2.finished`);
|
||||
// Add the pokemon back to party with Nature change
|
||||
playerPokemon.setCustomNature(encounter.misc.chosenNature);
|
||||
globalScene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature);
|
||||
|
||||
// Add pokemon and modifiers back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
optionSelectItems.push(option);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
};
|
||||
});
|
||||
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
return optionSelectItems;
|
||||
};
|
||||
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (): Promise<boolean> => {
|
||||
// Open menu for selecting pokemon and ability to learn
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Return the options for ability selection
|
||||
const speciesForm = !!pokemon.getFusionSpeciesForm()
|
||||
? pokemon.getFusionSpeciesForm()
|
||||
: pokemon.getSpeciesForm();
|
||||
const abilityCount = speciesForm.getAbilityCount();
|
||||
const abilities: Ability[] = new Array(abilityCount)
|
||||
.fill(null)
|
||||
.map((val, i) => allAbilities[speciesForm.getAbility(i)]);
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
};
|
||||
|
||||
const optionSelectItems: OptionSelectItem[] = [];
|
||||
abilities.forEach((ability: Ability, index) => {
|
||||
if (!optionSelectItems.some(o => o.label === ability.name)) {
|
||||
const option: OptionSelectItem = {
|
||||
label: ability.name,
|
||||
handler: () => {
|
||||
// Pokemon and ability selected
|
||||
encounter.setDialogueToken("ability", ability.name);
|
||||
encounter.misc = {
|
||||
playerPokemon: pokemon,
|
||||
abilityIndex: index,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
showEncounterText(ability.description, 0, 0, false);
|
||||
},
|
||||
};
|
||||
optionSelectItems.push(option);
|
||||
}
|
||||
});
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
|
||||
|
||||
return optionSelectItems;
|
||||
};
|
||||
// Spawn hard training session with chosen pokemon
|
||||
// Every 30 waves, add +1 boss segment, capping at 6
|
||||
// Also starts with +1 to all stats
|
||||
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 30), 6);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
|
||||
};
|
||||
const onBeforeRewardsPhase = () => {
|
||||
queueEncounterMessage(`${namespace}:option.3.finished`);
|
||||
// Add the pokemon back to party with ability change
|
||||
const abilityIndex = encounter.misc.abilityIndex;
|
||||
|
||||
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
|
||||
if (playerPokemon.getFusionSpeciesForm()) {
|
||||
playerPokemon.fusionAbilityIndex = abilityIndex;
|
||||
|
||||
// Spawn hard training session with chosen pokemon
|
||||
// Every 30 waves, add +1 boss segment, capping at 6
|
||||
// Also starts with +1 to all stats
|
||||
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 30), 6);
|
||||
const modifiers = new ModifiersHolder();
|
||||
const config = getEnemyConfig(playerPokemon, segments, modifiers);
|
||||
config.pokemonConfigs![0].tags = [
|
||||
BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON,
|
||||
];
|
||||
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
|
||||
|
||||
const onBeforeRewardsPhase = () => {
|
||||
queueEncounterMessage(`${namespace}:option.3.finished`);
|
||||
// Add the pokemon back to party with ability change
|
||||
const abilityIndex = encounter.misc.abilityIndex;
|
||||
|
||||
if (!!playerPokemon.getFusionSpeciesForm()) {
|
||||
playerPokemon.fusionAbilityIndex = abilityIndex;
|
||||
|
||||
// Only update the fusion's dex data if the Pokemon is already caught in dex (ignore rentals)
|
||||
const rootFusionSpecies = playerPokemon.fusionSpecies?.getRootSpeciesId();
|
||||
if (!isNullOrUndefined(rootFusionSpecies)
|
||||
&& speciesStarterCosts.hasOwnProperty(rootFusionSpecies)
|
||||
&& !!globalScene.gameData.dexData[rootFusionSpecies].caughtAttr) {
|
||||
globalScene.gameData.starterData[rootFusionSpecies].abilityAttr |= playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2
|
||||
// Only update the fusion's dex data if the Pokemon is already caught in dex (ignore rentals)
|
||||
const rootFusionSpecies = playerPokemon.fusionSpecies?.getRootSpeciesId();
|
||||
if (
|
||||
!isNullOrUndefined(rootFusionSpecies) &&
|
||||
speciesStarterCosts.hasOwnProperty(rootFusionSpecies) &&
|
||||
!!globalScene.gameData.dexData[rootFusionSpecies].caughtAttr
|
||||
) {
|
||||
globalScene.gameData.starterData[rootFusionSpecies].abilityAttr |=
|
||||
playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2
|
||||
? 1 << playerPokemon.fusionAbilityIndex
|
||||
: AbilityAttr.ABILITY_HIDDEN;
|
||||
}
|
||||
} else {
|
||||
playerPokemon.abilityIndex = abilityIndex;
|
||||
}
|
||||
} else {
|
||||
playerPokemon.abilityIndex = abilityIndex;
|
||||
}
|
||||
|
||||
playerPokemon.calculateStats();
|
||||
globalScene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
playerPokemon.calculateStats();
|
||||
globalScene.gameData.setPokemonCaught(playerPokemon, false);
|
||||
|
||||
// Add pokemon and mods back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
};
|
||||
// Add pokemon and mods back
|
||||
globalScene.getPlayerParty().push(playerPokemon);
|
||||
for (const mod of modifiers.value) {
|
||||
mod.pokemonId = playerPokemon.id;
|
||||
globalScene.addModifier(mod, true, false, false, true);
|
||||
}
|
||||
globalScene.updateModifiers(true);
|
||||
};
|
||||
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
|
||||
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.4.label`,
|
||||
buttonTooltip: `${namespace}:option.4.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.4.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
await initBattleWithEnemyConfig(config);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.4.label`,
|
||||
buttonTooltip: `${namespace}:option.4.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.4.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave encounter with no rewards or exp
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig {
|
||||
playerPokemon.resetSummonData();
|
||||
|
||||
// Passes modifiers by reference
|
||||
modifiers.value = playerPokemon.getHeldItems();
|
||||
const modifierConfigs = modifiers.value.map((mod) => {
|
||||
const modifierConfigs = modifiers.value.map(mod => {
|
||||
return {
|
||||
modifier: mod.clone(),
|
||||
isTransferable: false,
|
||||
stackCount: mod.stackCount
|
||||
stackCount: mod.stackCount,
|
||||
};
|
||||
}) as HeldModifierConfig[];
|
||||
|
||||
|
@ -410,6 +405,4 @@ function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifier
|
|||
|
||||
class ModifiersHolder {
|
||||
public value: PokemonHeldItemModifier[] = [];
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import type { EnemyPartyConfig, EnemyPokemonConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
loadCustomMovesForEncounter,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
@ -34,135 +41,149 @@ const SHOP_ITEM_COST_MULTIPLIER = 2.5;
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3809 | GitHub Issue #3809}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const TrashToTreasureEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRASH_TO_TREASURE)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(60, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withMaxAllowedEncounters(1)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.GARBODOR.toString() + "-gigantamax",
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: false,
|
||||
disableAnimation: true,
|
||||
scale: 1.5,
|
||||
y: 8,
|
||||
tint: 0.4
|
||||
}
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.TRASH_TO_TREASURE,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(60, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withMaxAllowedEncounters(1)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.GARBODOR.toString() + "-gigantamax",
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: false,
|
||||
disableAnimation: true,
|
||||
scale: 1.5,
|
||||
y: 8,
|
||||
tint: 0.4,
|
||||
},
|
||||
])
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Calculate boss mon (shiny locked)
|
||||
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
shiny: false, // Shiny lock because of custom intro sprite
|
||||
formIndex: 1, // Gmax
|
||||
bossSegmentModifier: 1, // +1 Segment from normal
|
||||
moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ]
|
||||
};
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveModifier: 0.5,
|
||||
pokemonConfigs: [ pokemonConfig ],
|
||||
disableSwitch: true
|
||||
};
|
||||
encounter.enemyPartyConfigs = [ config ];
|
||||
// Calculate boss mon (shiny locked)
|
||||
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
shiny: false, // Shiny lock because of custom intro sprite
|
||||
formIndex: 1, // Gmax
|
||||
bossSegmentModifier: 1, // +1 Segment from normal
|
||||
moveSet: [Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH],
|
||||
};
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveModifier: 0.5,
|
||||
pokemonConfigs: [pokemonConfig],
|
||||
disableSwitch: true,
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
// Load animations/sfx for Garbodor fight start moves
|
||||
loadCustomMovesForEncounter([ Moves.TOXIC, Moves.AMNESIA ]);
|
||||
// Load animations/sfx for Garbodor fight start moves
|
||||
loadCustomMovesForEncounter([Moves.TOXIC, Moves.AMNESIA]);
|
||||
|
||||
globalScene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav");
|
||||
globalScene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
|
||||
globalScene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav");
|
||||
globalScene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
|
||||
|
||||
encounter.setDialogueToken("costMultiplier", SHOP_ITEM_COST_MULTIPLIER.toString());
|
||||
encounter.setDialogueToken("costMultiplier", SHOP_ITEM_COST_MULTIPLIER.toString());
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Play Dig2 and then Venom Drench sfx
|
||||
doGarbageDig();
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Gain 2 Leftovers and 2 Shell Bell
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
await tryApplyDigRewardItems();
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Play Dig2 and then Venom Drench sfx
|
||||
doGarbageDig();
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Gain 2 Leftovers and 2 Shell Bell
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
await tryApplyDigRewardItems();
|
||||
|
||||
const blackSludge = generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [ SHOP_ITEM_COST_MULTIPLIER ]);
|
||||
const modifier = blackSludge?.newModifier();
|
||||
if (modifier) {
|
||||
await globalScene.addModifier(modifier, false, false, false, true);
|
||||
globalScene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 });
|
||||
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: modifier.type.name }), null, undefined, true);
|
||||
}
|
||||
const blackSludge = generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [
|
||||
SHOP_ITEM_COST_MULTIPLIER,
|
||||
]);
|
||||
const modifier = blackSludge?.newModifier();
|
||||
if (modifier) {
|
||||
await globalScene.addModifier(modifier, false, false, false, true);
|
||||
globalScene.playSound("battle_anims/PRSFX- Venom Drench", {
|
||||
volume: 2,
|
||||
});
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGain", {
|
||||
modifierName: modifier.type.name,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Investigate garbage, battle Gmax Garbodor
|
||||
globalScene.setFieldScale(0.75);
|
||||
await showEncounterText(`${namespace}:option.2.selected_2`);
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Investigate garbage, battle Gmax Garbodor
|
||||
globalScene.setFieldScale(0.75);
|
||||
await showEncounterText(`${namespace}:option.2.selected_2`);
|
||||
await transitionMysteryEncounterIntroVisuals();
|
||||
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true });
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [ BattlerIndex.PLAYER ],
|
||||
move: new PokemonMove(Moves.TOXIC),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [ BattlerIndex.ENEMY ],
|
||||
move: new PokemonMove(Moves.AMNESIA),
|
||||
ignorePp: true
|
||||
});
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT],
|
||||
fillRemaining: true,
|
||||
});
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.TOXIC),
|
||||
ignorePp: true,
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.ENEMY],
|
||||
move: new PokemonMove(Moves.AMNESIA),
|
||||
ignorePp: true,
|
||||
},
|
||||
);
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
||||
async function tryApplyDigRewardItems() {
|
||||
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
||||
|
@ -173,8 +194,10 @@ async function tryApplyDigRewardItems() {
|
|||
// Iterate over the party until an item was successfully given
|
||||
// First leftovers
|
||||
for (const pokemon of party) {
|
||||
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
|
||||
const heldItems = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
|
||||
|
||||
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
|
||||
|
@ -185,8 +208,10 @@ async function tryApplyDigRewardItems() {
|
|||
|
||||
// Second leftovers
|
||||
for (const pokemon of party) {
|
||||
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
|
||||
const heldItems = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
|
||||
|
||||
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
|
||||
|
@ -196,12 +221,22 @@ async function tryApplyDigRewardItems() {
|
|||
}
|
||||
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(i18next.t("battle:rewardGainCount", { modifierName: leftovers.name, count: 2 }), null, undefined, true);
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGainCount", {
|
||||
modifierName: leftovers.name,
|
||||
count: 2,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
// First Shell bell
|
||||
for (const pokemon of party) {
|
||||
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
|
||||
const heldItems = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
|
||||
|
||||
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) {
|
||||
|
@ -212,8 +247,10 @@ async function tryApplyDigRewardItems() {
|
|||
|
||||
// Second Shell bell
|
||||
for (const pokemon of party) {
|
||||
const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
|
||||
const heldItems = globalScene.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
|
||||
true,
|
||||
) as PokemonHeldItemModifier[];
|
||||
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
|
||||
|
||||
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) {
|
||||
|
@ -223,7 +260,15 @@ async function tryApplyDigRewardItems() {
|
|||
}
|
||||
|
||||
globalScene.playSound("item_fanfare");
|
||||
await showEncounterText(i18next.t("battle:rewardGainCount", { modifierName: shellBell.name, count: 2 }), null, undefined, true);
|
||||
await showEncounterText(
|
||||
i18next.t("battle:rewardGainCount", {
|
||||
modifierName: shellBell.name,
|
||||
count: 2,
|
||||
}),
|
||||
null,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
function doGarbageDig() {
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getRandomEncounterSpecies, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
getRandomEncounterSpecies,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterExp,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import type { EnemyPokemon } from "#app/field/pokemon";
|
||||
|
@ -9,10 +15,17 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|||
import { globalScene } from "#app/global-scene";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MoveRequirement, PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import {
|
||||
MoveRequirement,
|
||||
PersistentModifierRequirement,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import {
|
||||
catchPokemon,
|
||||
getHighestLevelPlayerPokemon,
|
||||
getSpriteKeysFromPokemon,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import type { Moves } from "#enums/moves";
|
||||
|
@ -34,223 +47,228 @@ const namespace = "mysteryEncounters/uncommonBreed";
|
|||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3811 | GitHub Issue #3811}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const UncommonBreedEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.UNCOMMON_BREED)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.UNCOMMON_BREED,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withFleeAllowed(false)
|
||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Calculate boss mon
|
||||
// Level equal to 2 below highest party member
|
||||
const level = getHighestLevelPlayerPokemon(false, true).level - 2;
|
||||
const pokemon = getRandomEncounterSpecies(level, true, true);
|
||||
// Calculate boss mon
|
||||
// Level equal to 2 below highest party member
|
||||
const level = getHighestLevelPlayerPokemon(false, true).level - 2;
|
||||
const pokemon = getRandomEncounterSpecies(level, true, true);
|
||||
|
||||
// Pokemon will always have one of its egg moves in its moveset
|
||||
const eggMoves = pokemon.getEggMoves();
|
||||
if (eggMoves) {
|
||||
const eggMoveIndex = randSeedInt(4);
|
||||
const randomEggMove: Moves = eggMoves[eggMoveIndex];
|
||||
encounter.misc = {
|
||||
eggMove: randomEggMove,
|
||||
pokemon: pokemon
|
||||
};
|
||||
if (pokemon.moveset.length < 4) {
|
||||
pokemon.moveset.push(new PokemonMove(randomEggMove));
|
||||
} else {
|
||||
pokemon.moveset[0] = new PokemonMove(randomEggMove);
|
||||
}
|
||||
// Pokemon will always have one of its egg moves in its moveset
|
||||
const eggMoves = pokemon.getEggMoves();
|
||||
if (eggMoves) {
|
||||
const eggMoveIndex = randSeedInt(4);
|
||||
const randomEggMove: Moves = eggMoves[eggMoveIndex];
|
||||
encounter.misc = {
|
||||
eggMove: randomEggMove,
|
||||
pokemon: pokemon,
|
||||
};
|
||||
if (pokemon.moveset.length < 4) {
|
||||
pokemon.moveset.push(new PokemonMove(randomEggMove));
|
||||
} else {
|
||||
encounter.misc.pokemon = pokemon;
|
||||
pokemon.moveset[0] = new PokemonMove(randomEggMove);
|
||||
}
|
||||
} else {
|
||||
encounter.misc.pokemon = pokemon;
|
||||
}
|
||||
|
||||
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
|
||||
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ?
|
||||
[ Stat.DEF, Stat.SPDEF, Stat.SPD ] :
|
||||
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
|
||||
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
|
||||
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
|
||||
globalScene.currentBattle.waveIndex < 50
|
||||
? [Stat.DEF, Stat.SPDEF, Stat.SPD]
|
||||
: [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
|
||||
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [{
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [
|
||||
{
|
||||
level: level,
|
||||
species: pokemon.species,
|
||||
dataSource: new PokemonData(pokemon),
|
||||
isBoss: false,
|
||||
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(`${namespace}:option.1.stat_boost`);
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
|
||||
}
|
||||
}],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [ config ];
|
||||
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(pokemon);
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
isShiny: pokemon.shiny,
|
||||
variant: pokemon.variant
|
||||
globalScene.unshiftPhase(
|
||||
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1),
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
encounter.setDialogueToken("enemyPokemon", pokemon.getNameToRender());
|
||||
globalScene.loadSe("PRSFX- Spotlight2", "battle_anims", "PRSFX- Spotlight2.wav");
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
// Animate the pokemon
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const pokemonSprite = encounter.introVisuals!.getSprites();
|
||||
|
||||
// Bounce at the end, then shiny sparkle if the Pokemon is shiny
|
||||
globalScene.tweens.add({
|
||||
targets: pokemonSprite,
|
||||
duration: 300,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
y: "-=20",
|
||||
loop: 1,
|
||||
onComplete: () => encounter.introVisuals?.playShinySparkles()
|
||||
});
|
||||
|
||||
globalScene.time.delayedCall(500, () => globalScene.playSound("battle_anims/PRSFX- Spotlight2"));
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(pokemon);
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true,
|
||||
isShiny: pokemon.shiny,
|
||||
variant: pokemon.variant,
|
||||
},
|
||||
];
|
||||
|
||||
encounter.setDialogueToken("enemyPokemon", pokemon.getNameToRender());
|
||||
globalScene.loadSe("PRSFX- Spotlight2", "battle_anims", "PRSFX- Spotlight2.wav");
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
// Animate the pokemon
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const pokemonSprite = encounter.introVisuals!.getSprites();
|
||||
|
||||
// Bounce at the end, then shiny sparkle if the Pokemon is shiny
|
||||
globalScene.tweens.add({
|
||||
targets: pokemonSprite,
|
||||
duration: 300,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
y: "-=20",
|
||||
loop: 1,
|
||||
onComplete: () => encounter.introVisuals?.playShinySparkles(),
|
||||
});
|
||||
|
||||
globalScene.time.delayedCall(500, () => globalScene.playSound("battle_anims/PRSFX- Spotlight2"));
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
const eggMove = encounter.misc.eggMove;
|
||||
if (!isNullOrUndefined(eggMove)) {
|
||||
// Check what type of move the egg move is to determine target
|
||||
const pokemonMove = new PokemonMove(eggMove);
|
||||
const move = pokemonMove.getMove();
|
||||
const target = move instanceof SelfStatusMove ? BattlerIndex.ENEMY : BattlerIndex.PLAYER;
|
||||
|
||||
encounter.startOfBattleEffects.push({
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [target],
|
||||
move: pokemonMove,
|
||||
ignorePp: true,
|
||||
});
|
||||
}
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
},
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Pick battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give it some food
|
||||
|
||||
const eggMove = encounter.misc.eggMove;
|
||||
if (!isNullOrUndefined(eggMove)) {
|
||||
// Check what type of move the egg move is to determine target
|
||||
const pokemonMove = new PokemonMove(eggMove);
|
||||
const move = pokemonMove.getMove();
|
||||
const target = move instanceof SelfStatusMove ? BattlerIndex.ENEMY : BattlerIndex.PLAYER;
|
||||
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [ target ],
|
||||
move: pokemonMove,
|
||||
ignorePp: true
|
||||
});
|
||||
// Remove 4 random berries from player's party
|
||||
// Get all player berry items, remove from party, and store reference
|
||||
const berryItems: BerryModifier[] = globalScene.findModifiers(
|
||||
m => m instanceof BerryModifier,
|
||||
) as BerryModifier[];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const index = randSeedInt(berryItems.length);
|
||||
const randBerry = berryItems[index];
|
||||
randBerry.stackCount--;
|
||||
if (randBerry.stackCount === 0) {
|
||||
globalScene.removeModifier(randBerry);
|
||||
berryItems.splice(index, 1);
|
||||
}
|
||||
}
|
||||
await globalScene.updateModifiers(true, true);
|
||||
|
||||
// Pokemon joins the team, with 2 egg moves
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const pokemon = encounter.misc.pokemon;
|
||||
|
||||
// Give 1 additional egg move
|
||||
givePokemonExtraEggMove(pokemon, encounter.misc.eggMove);
|
||||
|
||||
await catchPokemon(pokemon, null, PokeballType.POKEBALL, false);
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`
|
||||
}
|
||||
]
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Give it some food
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Attract the pokemon with a move
|
||||
// Pokemon joins the team, with 2 egg moves and IVs rolled an additional time
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const pokemon = encounter.misc.pokemon;
|
||||
|
||||
// Remove 4 random berries from player's party
|
||||
// Get all player berry items, remove from party, and store reference
|
||||
const berryItems: BerryModifier[] = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const index = randSeedInt(berryItems.length);
|
||||
const randBerry = berryItems[index];
|
||||
randBerry.stackCount--;
|
||||
if (randBerry.stackCount === 0) {
|
||||
globalScene.removeModifier(randBerry);
|
||||
berryItems.splice(index, 1);
|
||||
}
|
||||
}
|
||||
await globalScene.updateModifiers(true, true);
|
||||
// Give 1 additional egg move
|
||||
givePokemonExtraEggMove(pokemon, encounter.misc.eggMove);
|
||||
|
||||
// Pokemon joins the team, with 2 egg moves
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const pokemon = encounter.misc.pokemon;
|
||||
// Roll IVs a second time
|
||||
pokemon.ivs = pokemon.ivs.map(iv => {
|
||||
const newValue = randSeedInt(31);
|
||||
return newValue > iv ? newValue : iv;
|
||||
});
|
||||
|
||||
// Give 1 additional egg move
|
||||
givePokemonExtraEggMove(pokemon, encounter.misc.eggMove);
|
||||
|
||||
await catchPokemon(pokemon, null, PokeballType.POKEBALL, false);
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`
|
||||
}
|
||||
]
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Attract the pokemon with a move
|
||||
// Pokemon joins the team, with 2 egg moves and IVs rolled an additional time
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const pokemon = encounter.misc.pokemon;
|
||||
|
||||
// Give 1 additional egg move
|
||||
givePokemonExtraEggMove(pokemon, encounter.misc.eggMove);
|
||||
|
||||
// Roll IVs a second time
|
||||
pokemon.ivs = pokemon.ivs.map(iv => {
|
||||
const newValue = randSeedInt(31);
|
||||
return newValue > iv ? newValue : iv;
|
||||
});
|
||||
|
||||
await catchPokemon(pokemon, null, PokeballType.POKEBALL, false);
|
||||
if (encounter.selectedOption?.primaryPokemon?.id) {
|
||||
setEncounterExp(encounter.selectedOption.primaryPokemon.id, pokemon.getExpValue(), false);
|
||||
}
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
await catchPokemon(pokemon, null, PokeballType.POKEBALL, false);
|
||||
if (encounter.selectedOption?.primaryPokemon?.id) {
|
||||
setEncounterExp(encounter.selectedOption.primaryPokemon.id, pokemon.getExpValue(), false);
|
||||
}
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
||||
function givePokemonExtraEggMove(pokemon: EnemyPokemon, previousEggMove: Moves) {
|
||||
const eggMoves = pokemon.getEggMoves();
|
||||
|
|
|
@ -6,7 +6,12 @@ import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounte
|
|||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
|
||||
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils";
|
||||
import {
|
||||
generateModifierType,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
} from "../utils/encounter-phase-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
|
@ -23,7 +28,10 @@ import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-
|
|||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
|
||||
import {
|
||||
doPokemonTransformationSequence,
|
||||
TransformationScreenPosition,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
|
||||
import { getLevelTotalExp } from "#app/data/exp";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
|
@ -104,230 +112,260 @@ const PERCENT_LEVEL_LOSS_ON_REFUSE = 10;
|
|||
* Value ranges of the resulting species BST transformations after adding values to original species
|
||||
* 2 Pokemon in the party use this range
|
||||
*/
|
||||
const HIGH_BST_TRANSFORM_BASE_VALUES: [number, number] = [ 90, 110 ];
|
||||
const HIGH_BST_TRANSFORM_BASE_VALUES: [number, number] = [90, 110];
|
||||
/**
|
||||
* Value ranges of the resulting species BST transformations after adding values to original species
|
||||
* All remaining Pokemon in the party use this range
|
||||
*/
|
||||
const STANDARD_BST_TRANSFORM_BASE_VALUES: [number, number] = [ 40, 50 ];
|
||||
const STANDARD_BST_TRANSFORM_BASE_VALUES: [number, number] = [40, 50];
|
||||
|
||||
/**
|
||||
* Weird Dream encounter.
|
||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3822 | GitHub Issue #3822}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const WeirdDreamEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION)
|
||||
// TODO: should reset minimum wave to 10 when there are more Rogue tiers in pool. Matching Dark Deal minimum for now.
|
||||
.withSceneWaveRangeRequirement(30, 140)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "weird_dream_woman",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 11,
|
||||
yShadow: 6,
|
||||
x: 4
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
globalScene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3");
|
||||
export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.WEIRD_DREAM,
|
||||
)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION)
|
||||
// TODO: should reset minimum wave to 10 when there are more Rogue tiers in pool. Matching Dark Deal minimum for now.
|
||||
.withSceneWaveRangeRequirement(30, 140)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "weird_dream_woman",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 11,
|
||||
yShadow: 6,
|
||||
x: 4,
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOnInit(() => {
|
||||
globalScene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3");
|
||||
|
||||
// Calculate all the newly transformed Pokemon and begin asset load
|
||||
const teamTransformations = getTeamTransformations();
|
||||
const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets());
|
||||
globalScene.currentBattle.mysteryEncounter!.misc = {
|
||||
teamTransformations,
|
||||
loadAssets
|
||||
// Calculate all the newly transformed Pokemon and begin asset load
|
||||
const teamTransformations = getTeamTransformations();
|
||||
const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets());
|
||||
globalScene.currentBattle.mysteryEncounter!.misc = {
|
||||
teamTransformations,
|
||||
loadAssets,
|
||||
};
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
globalScene.fadeAndSwitchBgm("mystery_encounter_weird_dream");
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Play the animation as the player goes through the dialogue
|
||||
globalScene.time.delayedCall(1000, () => {
|
||||
doShowDreamBackground();
|
||||
});
|
||||
|
||||
for (const transformation of globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations) {
|
||||
globalScene.removePokemonFromPlayerParty(transformation.previousPokemon, false);
|
||||
globalScene.getPlayerParty().push(transformation.newPokemon);
|
||||
}
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue
|
||||
const cutsceneDialoguePromise = showEncounterText(`${namespace}:option.1.cutscene`);
|
||||
|
||||
// Change the entire player's party
|
||||
// Wait for all new Pokemon assets to be loaded before showing transformation animations
|
||||
await Promise.all(globalScene.currentBattle.mysteryEncounter!.misc.loadAssets);
|
||||
const transformations = globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations;
|
||||
|
||||
// If there are 1-3 transformations, do them centered back to back
|
||||
// Otherwise, the first 3 transformations are executed side-by-side, then any remaining 1-3 transformations occur in those same respective positions
|
||||
if (transformations.length <= 3) {
|
||||
for (const transformation of transformations) {
|
||||
const pokemon1 = transformation.previousPokemon;
|
||||
const pokemon2 = transformation.newPokemon;
|
||||
|
||||
await doPokemonTransformationSequence(pokemon1, pokemon2, TransformationScreenPosition.CENTER);
|
||||
}
|
||||
} else {
|
||||
await doSideBySideTransformations(transformations);
|
||||
}
|
||||
|
||||
// Make sure player has finished cutscene dialogue
|
||||
await cutsceneDialoguePromise;
|
||||
|
||||
doHideDreamBackground();
|
||||
await showEncounterText(`${namespace}:option.1.dream_complete`);
|
||||
|
||||
await doNewTeamPostProcess(transformations);
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [
|
||||
modifierTypes.MEMORY_MUSHROOM,
|
||||
modifierTypes.ROGUE_BALL,
|
||||
modifierTypes.MINT,
|
||||
modifierTypes.MINT,
|
||||
modifierTypes.MINT,
|
||||
],
|
||||
fillRemaining: false,
|
||||
});
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Battle your "future" team for some item rewards
|
||||
const transformations: PokemonTransformation[] =
|
||||
globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations;
|
||||
|
||||
// Uses the pokemon that player's party would have transformed into
|
||||
const enemyPokemonConfigs: EnemyPokemonConfig[] = [];
|
||||
for (const transformation of transformations) {
|
||||
const newPokemon = transformation.newPokemon;
|
||||
const previousPokemon = transformation.previousPokemon;
|
||||
|
||||
await postProcessTransformedPokemon(previousPokemon, newPokemon, newPokemon.species.getRootSpeciesId(), true);
|
||||
|
||||
const dataSource = new PokemonData(newPokemon);
|
||||
dataSource.player = false;
|
||||
|
||||
// Copy held items to new pokemon
|
||||
const newPokemonHeldItemConfigs: HeldModifierConfig[] = [];
|
||||
for (const item of transformation.heldItems) {
|
||||
newPokemonHeldItemConfigs.push({
|
||||
modifier: item.clone() as PokemonHeldItemModifier,
|
||||
stackCount: item.getStackCount(),
|
||||
isTransferable: false,
|
||||
});
|
||||
}
|
||||
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
||||
if (shouldGetOldGateau(newPokemon)) {
|
||||
const stats = getOldGateauBoostedStats(newPokemon);
|
||||
newPokemonHeldItemConfigs.push({
|
||||
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [
|
||||
OLD_GATEAU_STATS_UP,
|
||||
stats,
|
||||
]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false,
|
||||
});
|
||||
}
|
||||
|
||||
const enemyConfig: EnemyPokemonConfig = {
|
||||
species: transformation.newSpecies,
|
||||
isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD,
|
||||
level: previousPokemon.level,
|
||||
dataSource: dataSource,
|
||||
modifierConfigs: newPokemonHeldItemConfigs,
|
||||
};
|
||||
|
||||
enemyPokemonConfigs.push(enemyConfig);
|
||||
}
|
||||
|
||||
const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET;
|
||||
const trainerConfig =
|
||||
trainerConfigs[
|
||||
genderIndex === PlayerGender.FEMALE ? TrainerType.FUTURE_SELF_F : TrainerType.FUTURE_SELF_M
|
||||
].clone();
|
||||
trainerConfig.setPartyTemplates(new TrainerPartyTemplate(transformations.length, PartyMemberStrength.STRONG));
|
||||
const enemyPartyConfig: EnemyPartyConfig = {
|
||||
trainerConfig: trainerConfig,
|
||||
pokemonConfigs: enemyPokemonConfigs,
|
||||
female: genderIndex === PlayerGender.FEMALE,
|
||||
};
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart(() => {
|
||||
globalScene.fadeAndSwitchBgm("mystery_encounter_weird_dream");
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.1.selected`,
|
||||
}
|
||||
const onBeforeRewards = () => {
|
||||
// Before battle rewards, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently)
|
||||
// One random pokemon will get its passive unlocked
|
||||
const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive);
|
||||
if (passiveDisabledPokemon?.length > 0) {
|
||||
const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)];
|
||||
enablePassiveMon.passive = true;
|
||||
enablePassiveMon.updateInfo(true);
|
||||
}
|
||||
};
|
||||
|
||||
setEncounterRewards(
|
||||
{
|
||||
guaranteedModifierTiers: [
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ROGUE,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.ULTRA,
|
||||
ModifierTier.GREAT,
|
||||
ModifierTier.GREAT,
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Play the animation as the player goes through the dialogue
|
||||
globalScene.time.delayedCall(1000, () => {
|
||||
doShowDreamBackground();
|
||||
});
|
||||
fillRemaining: false,
|
||||
},
|
||||
undefined,
|
||||
onBeforeRewards,
|
||||
);
|
||||
|
||||
for (const transformation of globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations) {
|
||||
globalScene.removePokemonFromPlayerParty(transformation.previousPokemon, false);
|
||||
globalScene.getPlayerParty().push(transformation.newPokemon);
|
||||
}
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue
|
||||
const cutsceneDialoguePromise = showEncounterText(`${namespace}:option.1.cutscene`);
|
||||
await showEncounterText(`${namespace}:option.2.selected_2`, null, undefined, true);
|
||||
await initBattleWithEnemyConfig(enemyPartyConfig);
|
||||
},
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave, reduce party levels by 10%
|
||||
for (const pokemon of globalScene.getPlayerParty()) {
|
||||
pokemon.level = Math.max(Math.ceil(((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100) * pokemon.level), 1);
|
||||
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
|
||||
pokemon.levelExp = 0;
|
||||
|
||||
// Change the entire player's party
|
||||
// Wait for all new Pokemon assets to be loaded before showing transformation animations
|
||||
await Promise.all(globalScene.currentBattle.mysteryEncounter!.misc.loadAssets);
|
||||
const transformations = globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations;
|
||||
|
||||
// If there are 1-3 transformations, do them centered back to back
|
||||
// Otherwise, the first 3 transformations are executed side-by-side, then any remaining 1-3 transformations occur in those same respective positions
|
||||
if (transformations.length <= 3) {
|
||||
for (const transformation of transformations) {
|
||||
const pokemon1 = transformation.previousPokemon;
|
||||
const pokemon2 = transformation.newPokemon;
|
||||
|
||||
await doPokemonTransformationSequence(pokemon1, pokemon2, TransformationScreenPosition.CENTER);
|
||||
}
|
||||
} else {
|
||||
await doSideBySideTransformations(transformations);
|
||||
}
|
||||
|
||||
// Make sure player has finished cutscene dialogue
|
||||
await cutsceneDialoguePromise;
|
||||
|
||||
doHideDreamBackground();
|
||||
await showEncounterText(`${namespace}:option.1.dream_complete`);
|
||||
|
||||
await doNewTeamPostProcess(transformations);
|
||||
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT, modifierTypes.MINT ], fillRemaining: false });
|
||||
leaveEncounterWithoutBattle(true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Battle your "future" team for some item rewards
|
||||
const transformations: PokemonTransformation[] = globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations;
|
||||
|
||||
// Uses the pokemon that player's party would have transformed into
|
||||
const enemyPokemonConfigs: EnemyPokemonConfig[] = [];
|
||||
for (const transformation of transformations) {
|
||||
const newPokemon = transformation.newPokemon;
|
||||
const previousPokemon = transformation.previousPokemon;
|
||||
|
||||
await postProcessTransformedPokemon(previousPokemon, newPokemon, newPokemon.species.getRootSpeciesId(), true);
|
||||
|
||||
const dataSource = new PokemonData(newPokemon);
|
||||
dataSource.player = false;
|
||||
|
||||
// Copy held items to new pokemon
|
||||
const newPokemonHeldItemConfigs: HeldModifierConfig[] = [];
|
||||
for (const item of transformation.heldItems) {
|
||||
newPokemonHeldItemConfigs.push({
|
||||
modifier: item.clone() as PokemonHeldItemModifier,
|
||||
stackCount: item.getStackCount(),
|
||||
isTransferable: false
|
||||
});
|
||||
}
|
||||
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
||||
if (shouldGetOldGateau(newPokemon)) {
|
||||
const stats = getOldGateauBoostedStats(newPokemon);
|
||||
newPokemonHeldItemConfigs.push({
|
||||
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [ OLD_GATEAU_STATS_UP, stats ]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1,
|
||||
isTransferable: false
|
||||
});
|
||||
}
|
||||
|
||||
const enemyConfig: EnemyPokemonConfig = {
|
||||
species: transformation.newSpecies,
|
||||
isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD,
|
||||
level: previousPokemon.level,
|
||||
dataSource: dataSource,
|
||||
modifierConfigs: newPokemonHeldItemConfigs
|
||||
};
|
||||
|
||||
enemyPokemonConfigs.push(enemyConfig);
|
||||
}
|
||||
|
||||
const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET;
|
||||
const trainerConfig = trainerConfigs[genderIndex === PlayerGender.FEMALE ? TrainerType.FUTURE_SELF_F : TrainerType.FUTURE_SELF_M].clone();
|
||||
trainerConfig.setPartyTemplates(new TrainerPartyTemplate(transformations.length, PartyMemberStrength.STRONG));
|
||||
const enemyPartyConfig: EnemyPartyConfig = {
|
||||
trainerConfig: trainerConfig,
|
||||
pokemonConfigs: enemyPokemonConfigs,
|
||||
female: genderIndex === PlayerGender.FEMALE
|
||||
};
|
||||
|
||||
const onBeforeRewards = () => {
|
||||
// Before battle rewards, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently)
|
||||
// One random pokemon will get its passive unlocked
|
||||
const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive);
|
||||
if (passiveDisabledPokemon?.length > 0) {
|
||||
const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)];
|
||||
enablePassiveMon.passive = true;
|
||||
enablePassiveMon.updateInfo(true);
|
||||
}
|
||||
};
|
||||
|
||||
setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: false }, undefined, onBeforeRewards);
|
||||
|
||||
await showEncounterText(`${namespace}:option.2.selected_2`, null, undefined, true);
|
||||
await initBattleWithEnemyConfig(enemyPartyConfig);
|
||||
pokemon.calculateStats();
|
||||
pokemon.getBattleInfo().setLevel(pokemon.level);
|
||||
await pokemon.updateInfo();
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Leave, reduce party levels by 10%
|
||||
for (const pokemon of globalScene.getPlayerParty()) {
|
||||
pokemon.level = Math.max(Math.ceil((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100 * pokemon.level), 1);
|
||||
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
|
||||
pokemon.levelExp = 0;
|
||||
|
||||
pokemon.calculateStats();
|
||||
pokemon.getBattleInfo().setLevel(pokemon.level);
|
||||
await pokemon.updateInfo();
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.build();
|
||||
leaveEncounterWithoutBattle(true);
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
interface PokemonTransformation {
|
||||
previousPokemon: PlayerPokemon;
|
||||
|
@ -342,7 +380,7 @@ function getTeamTransformations(): PokemonTransformation[] {
|
|||
const alreadyUsedSpecies: PokemonSpecies[] = party.map(p => p.species);
|
||||
const pokemonTransformations: PokemonTransformation[] = party.map(p => {
|
||||
return {
|
||||
previousPokemon: p
|
||||
previousPokemon: p,
|
||||
} as PokemonTransformation;
|
||||
});
|
||||
|
||||
|
@ -358,7 +396,9 @@ function getTeamTransformations(): PokemonTransformation[] {
|
|||
for (let i = 0; i < numPokemon; i++) {
|
||||
const removed = removedPokemon[i];
|
||||
const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id);
|
||||
pokemonTransformations[index].heldItems = removed.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
pokemonTransformations[index].heldItems = removed
|
||||
.getHeldItems()
|
||||
.filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
|
||||
const bst = removed.getSpeciesForm().getBaseStatTotal();
|
||||
let newBstRange: [number, number];
|
||||
|
@ -368,7 +408,13 @@ function getTeamTransformations(): PokemonTransformation[] {
|
|||
newBstRange = STANDARD_BST_TRANSFORM_BASE_VALUES;
|
||||
}
|
||||
|
||||
const newSpecies = getTransformedSpecies(bst, newBstRange, hasPokemonInSuperLegendaryBstThreshold, hasPokemonInLegendaryBstThreshold, alreadyUsedSpecies);
|
||||
const newSpecies = getTransformedSpecies(
|
||||
bst,
|
||||
newBstRange,
|
||||
hasPokemonInSuperLegendaryBstThreshold,
|
||||
hasPokemonInLegendaryBstThreshold,
|
||||
alreadyUsedSpecies,
|
||||
);
|
||||
|
||||
const newSpeciesBst = newSpecies.getBaseStatTotal();
|
||||
if (newSpeciesBst > SUPER_LEGENDARY_BST_THRESHOLD) {
|
||||
|
@ -378,7 +424,6 @@ function getTeamTransformations(): PokemonTransformation[] {
|
|||
hasPokemonInLegendaryBstThreshold = true;
|
||||
}
|
||||
|
||||
|
||||
pokemonTransformations[index].newSpecies = newSpecies;
|
||||
console.log("New species: " + JSON.stringify(newSpecies));
|
||||
alreadyUsedSpecies.push(newSpecies);
|
||||
|
@ -386,7 +431,12 @@ function getTeamTransformations(): PokemonTransformation[] {
|
|||
|
||||
for (const transformation of pokemonTransformations) {
|
||||
const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount());
|
||||
transformation.newPokemon = globalScene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined);
|
||||
transformation.newPokemon = globalScene.addPlayerPokemon(
|
||||
transformation.newSpecies,
|
||||
transformation.previousPokemon.level,
|
||||
newAbilityIndex,
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
|
||||
return pokemonTransformations;
|
||||
|
@ -411,8 +461,9 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
|
|||
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
||||
if (shouldGetOldGateau(newPokemon)) {
|
||||
const stats = getOldGateauBoostedStats(newPokemon);
|
||||
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU()
|
||||
.generateType(globalScene.getPlayerParty(), [ OLD_GATEAU_STATS_UP, stats ])
|
||||
const modType = modifierTypes
|
||||
.MYSTERY_ENCOUNTER_OLD_GATEAU()
|
||||
.generateType(globalScene.getPlayerParty(), [OLD_GATEAU_STATS_UP, stats])
|
||||
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
|
||||
const modifier = modType?.newModifier(newPokemon);
|
||||
if (modifier) {
|
||||
|
@ -446,7 +497,12 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
|
|||
* @param speciesRootForm
|
||||
* @param forBattle Default `false`. If false, will perform achievements and dex unlocks for the player.
|
||||
*/
|
||||
async function postProcessTransformedPokemon(previousPokemon: PlayerPokemon, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise<boolean> {
|
||||
async function postProcessTransformedPokemon(
|
||||
previousPokemon: PlayerPokemon,
|
||||
newPokemon: PlayerPokemon,
|
||||
speciesRootForm: Species,
|
||||
forBattle = false,
|
||||
): Promise<boolean> {
|
||||
let isNewStarter = false;
|
||||
// Roll HA a second time
|
||||
if (newPokemon.species.abilityHidden) {
|
||||
|
@ -470,11 +526,17 @@ async function postProcessTransformedPokemon(previousPokemon: PlayerPokemon, new
|
|||
});
|
||||
|
||||
// Roll a neutral nature
|
||||
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
|
||||
if (!forBattle && (newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny())) {
|
||||
if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) {
|
||||
if (
|
||||
!forBattle &&
|
||||
(newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny())
|
||||
) {
|
||||
if (
|
||||
newPokemon.getSpeciesForm().abilityHidden &&
|
||||
newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1
|
||||
) {
|
||||
globalScene.validateAchv(achvs.HIDDEN_ABILITY);
|
||||
}
|
||||
|
||||
|
@ -494,7 +556,11 @@ async function postProcessTransformedPokemon(previousPokemon: PlayerPokemon, new
|
|||
const newStarterUnlocked = await globalScene.gameData.setPokemonCaught(newPokemon, true, false, false);
|
||||
if (newStarterUnlocked) {
|
||||
isNewStarter = true;
|
||||
await showEncounterText(i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() }));
|
||||
await showEncounterText(
|
||||
i18next.t("battle:addedAsAStarter", {
|
||||
pokemonName: getPokemonSpecies(speciesRootForm).getName(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -528,7 +594,7 @@ async function postProcessTransformedPokemon(previousPokemon: PlayerPokemon, new
|
|||
|
||||
// Randomize the second type of the pokemon
|
||||
// If the pokemon does not normally have a second type, it will gain 1
|
||||
const newTypes = [ PokemonType.UNKNOWN ];
|
||||
const newTypes = [PokemonType.UNKNOWN];
|
||||
let newType = randSeedInt(18) as PokemonType;
|
||||
while (newType === newTypes[0]) {
|
||||
newType = randSeedInt(18) as PokemonType;
|
||||
|
@ -568,23 +634,30 @@ function getOldGateauBoostedStats(pokemon: Pokemon): Stat[] {
|
|||
return stats;
|
||||
}
|
||||
|
||||
|
||||
function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies {
|
||||
function getTransformedSpecies(
|
||||
originalBst: number,
|
||||
bstSearchRange: [number, number],
|
||||
hasPokemonBstHigherThan600: boolean,
|
||||
hasPokemonBstBetween570And600: boolean,
|
||||
alreadyUsedSpecies: PokemonSpecies[],
|
||||
): PokemonSpecies {
|
||||
let newSpecies: PokemonSpecies | undefined;
|
||||
while (isNullOrUndefined(newSpecies)) {
|
||||
const bstCap = originalBst + bstSearchRange[1];
|
||||
const bstMin = Math.max(originalBst + bstSearchRange[0], 0);
|
||||
|
||||
// Get any/all species that fall within the Bst range requirements
|
||||
let validSpecies = allSpecies
|
||||
.filter(s => {
|
||||
const speciesBst = s.getBaseStatTotal();
|
||||
const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap;
|
||||
// Checks that a Pokemon has not already been added in the +600 or 570-600 slots;
|
||||
const validBst = (!hasPokemonBstBetween570And600 || (speciesBst < NON_LEGENDARY_BST_THRESHOLD || speciesBst > SUPER_LEGENDARY_BST_THRESHOLD)) &&
|
||||
(!hasPokemonBstHigherThan600 || speciesBst <= SUPER_LEGENDARY_BST_THRESHOLD);
|
||||
return bstInRange && validBst && !EXCLUDED_TRANSFORMATION_SPECIES.includes(s.speciesId);
|
||||
});
|
||||
let validSpecies = allSpecies.filter(s => {
|
||||
const speciesBst = s.getBaseStatTotal();
|
||||
const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap;
|
||||
// Checks that a Pokemon has not already been added in the +600 or 570-600 slots;
|
||||
const validBst =
|
||||
(!hasPokemonBstBetween570And600 ||
|
||||
speciesBst < NON_LEGENDARY_BST_THRESHOLD ||
|
||||
speciesBst > SUPER_LEGENDARY_BST_THRESHOLD) &&
|
||||
(!hasPokemonBstHigherThan600 || speciesBst <= SUPER_LEGENDARY_BST_THRESHOLD);
|
||||
return bstInRange && validBst && !EXCLUDED_TRANSFORMATION_SPECIES.includes(s.speciesId);
|
||||
});
|
||||
|
||||
// There must be at least 20 species available before it will choose one
|
||||
if (validSpecies?.length > 20) {
|
||||
|
@ -608,7 +681,13 @@ function doShowDreamBackground() {
|
|||
transformationContainer.name = "Dream Background";
|
||||
|
||||
// In case it takes a bit for video to load
|
||||
const transformationStaticBg = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0);
|
||||
const transformationStaticBg = globalScene.add.rectangle(
|
||||
0,
|
||||
0,
|
||||
globalScene.game.canvas.width / 6,
|
||||
globalScene.game.canvas.height / 6,
|
||||
0,
|
||||
);
|
||||
transformationStaticBg.setName("Black Background");
|
||||
transformationStaticBg.setOrigin(0, 0);
|
||||
transformationContainer.add(transformationStaticBg);
|
||||
|
@ -631,7 +710,7 @@ function doShowDreamBackground() {
|
|||
targets: transformationContainer,
|
||||
alpha: 1,
|
||||
duration: 3000,
|
||||
ease: "Sine.easeInOut"
|
||||
ease: "Sine.easeInOut",
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -645,7 +724,7 @@ function doHideDreamBackground() {
|
|||
ease: "Sine.easeInOut",
|
||||
onComplete: () => {
|
||||
globalScene.fieldUI.remove(transformationContainer, true);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -660,16 +739,15 @@ function doSideBySideTransformations(transformations: PokemonTransformation[]) {
|
|||
const pokemon2 = transformation.newPokemon;
|
||||
const screenPosition = i as TransformationScreenPosition;
|
||||
|
||||
const transformationPromise = doPokemonTransformationSequence(pokemon1, pokemon2, screenPosition)
|
||||
.then(() => {
|
||||
if (transformations.length > i + 3) {
|
||||
const nextTransformationAtPosition = transformations[i + 3];
|
||||
const nextPokemon1 = nextTransformationAtPosition.previousPokemon;
|
||||
const nextPokemon2 = nextTransformationAtPosition.newPokemon;
|
||||
const transformationPromise = doPokemonTransformationSequence(pokemon1, pokemon2, screenPosition).then(() => {
|
||||
if (transformations.length > i + 3) {
|
||||
const nextTransformationAtPosition = transformations[i + 3];
|
||||
const nextPokemon1 = nextTransformationAtPosition.previousPokemon;
|
||||
const nextPokemon2 = nextTransformationAtPosition.newPokemon;
|
||||
|
||||
allTransformationPromises.push(doPokemonTransformationSequence(nextPokemon1, nextPokemon2, screenPosition));
|
||||
}
|
||||
});
|
||||
allTransformationPromises.push(doPokemonTransformationSequence(nextPokemon1, nextPokemon2, screenPosition));
|
||||
}
|
||||
});
|
||||
allTransformationPromises.push(transformationPromise);
|
||||
});
|
||||
}
|
||||
|
@ -691,11 +769,15 @@ function doSideBySideTransformations(transformations: PokemonTransformation[]) {
|
|||
* @param newPokemon
|
||||
* @param speciesRootForm
|
||||
*/
|
||||
async function addEggMoveToNewPokemonMoveset(newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise<number | null> {
|
||||
async function addEggMoveToNewPokemonMoveset(
|
||||
newPokemon: PlayerPokemon,
|
||||
speciesRootForm: Species,
|
||||
forBattle = false,
|
||||
): Promise<number | null> {
|
||||
let eggMoveIndex: null | number = null;
|
||||
const eggMoves = newPokemon.getEggMoves()?.slice(0);
|
||||
if (eggMoves) {
|
||||
const eggMoveIndices = randSeedShuffle([ 0, 1, 2, 3 ]);
|
||||
const eggMoveIndices = randSeedShuffle([0, 1, 2, 3]);
|
||||
let randomEggMoveIndex = eggMoveIndices.pop();
|
||||
let randomEggMove = !isNullOrUndefined(randomEggMoveIndex) ? eggMoves[randomEggMoveIndex] : null;
|
||||
let retries = 0;
|
||||
|
@ -717,7 +799,11 @@ async function addEggMoveToNewPokemonMoveset(newPokemon: PlayerPokemon, speciesR
|
|||
}
|
||||
|
||||
// For pokemon that the player owns (including ones just caught), unlock the egg move
|
||||
if (!forBattle && !isNullOrUndefined(randomEggMoveIndex) && !!globalScene.gameData.dexData[speciesRootForm].caughtAttr) {
|
||||
if (
|
||||
!forBattle &&
|
||||
!isNullOrUndefined(randomEggMoveIndex) &&
|
||||
!!globalScene.gameData.dexData[speciesRootForm].caughtAttr
|
||||
) {
|
||||
await globalScene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true);
|
||||
}
|
||||
}
|
||||
|
@ -732,11 +818,18 @@ async function addEggMoveToNewPokemonMoveset(newPokemon: PlayerPokemon, speciesR
|
|||
* @param newPokemonGeneratedMoveset
|
||||
* @param newEggMoveIndex
|
||||
*/
|
||||
function addFavoredMoveToNewPokemonMoveset(newPokemon: PlayerPokemon, newPokemonGeneratedMoveset: (PokemonMove | null)[], newEggMoveIndex: number | null) {
|
||||
function addFavoredMoveToNewPokemonMoveset(
|
||||
newPokemon: PlayerPokemon,
|
||||
newPokemonGeneratedMoveset: (PokemonMove | null)[],
|
||||
newEggMoveIndex: number | null,
|
||||
) {
|
||||
let favoredMove: PokemonMove | null = null;
|
||||
for (const move of newPokemonGeneratedMoveset) {
|
||||
// Needs to match first type, second type will be replaced
|
||||
if (move?.getMove().type === newPokemon.getTypes()[0] && !newPokemon.moveset.some(m => m?.moveId === move?.moveId)) {
|
||||
if (
|
||||
move?.getMove().type === newPokemon.getTypes()[0] &&
|
||||
!newPokemon.moveset.some(m => m?.moveId === move?.moveId)
|
||||
) {
|
||||
favoredMove = move;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -72,4 +72,3 @@ export default class MysteryEncounterDialogue {
|
|||
encounterOptionsDialogue?: EncounterOptionsDialogue;
|
||||
outro?: TextDisplay[];
|
||||
}
|
||||
|
||||
|
|
|
@ -4,13 +4,18 @@ import type { PlayerPokemon } from "#app/field/pokemon";
|
|||
import type Pokemon from "#app/field/pokemon";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
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 { CanLearnMoveRequirement } from "./requirements/can-learn-move-requirement";
|
||||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
||||
|
||||
// biome-ignore lint/suspicious/noConfusingVoidType: void unions in callbacks are OK
|
||||
export type OptionPhaseCallback = () => Promise<void | boolean>;
|
||||
|
||||
/**
|
||||
|
@ -71,16 +76,22 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
|
|||
* Returns true if option contains any {@linkcode EncounterRequirement}s, false otherwise.
|
||||
*/
|
||||
hasRequirements(): boolean {
|
||||
return this.requirements.length > 0 || this.primaryPokemonRequirements.length > 0 || this.secondaryPokemonRequirements.length > 0;
|
||||
return (
|
||||
this.requirements.length > 0 ||
|
||||
this.primaryPokemonRequirements.length > 0 ||
|
||||
this.secondaryPokemonRequirements.length > 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all {@linkcode EncounterRequirement}s for the option are met
|
||||
*/
|
||||
meetsRequirements(): boolean {
|
||||
return !this.requirements.some(requirement => !requirement.meetsRequirement())
|
||||
&& this.meetsSupportingRequirementAndSupportingPokemonSelected()
|
||||
&& this.meetsPrimaryRequirementAndPrimaryPokemonSelected();
|
||||
return (
|
||||
!this.requirements.some(requirement => !requirement.meetsRequirement()) &&
|
||||
this.meetsSupportingRequirementAndSupportingPokemonSelected() &&
|
||||
this.meetsPrimaryRequirementAndPrimaryPokemonSelected()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,7 +99,13 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
|
|||
* @param pokemon
|
||||
*/
|
||||
pokemonMeetsPrimaryRequirements(pokemon: Pokemon): boolean {
|
||||
return !this.primaryPokemonRequirements.some(req => !req.queryParty(globalScene.getPlayerParty()).map(p => p.id).includes(pokemon.id));
|
||||
return !this.primaryPokemonRequirements.some(
|
||||
req =>
|
||||
!req
|
||||
.queryParty(globalScene.getPlayerParty())
|
||||
.map(p => p.id)
|
||||
.includes(pokemon.id),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,27 +142,26 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
|
|||
} else {
|
||||
overlap.push(qp);
|
||||
}
|
||||
|
||||
}
|
||||
if (truePrimaryPool.length > 0) {
|
||||
// always choose from the non-overlapping pokemon first
|
||||
this.primaryPokemon = truePrimaryPool[randSeedInt(truePrimaryPool.length)];
|
||||
return true;
|
||||
} else {
|
||||
// if there are multiple overlapping pokemon, we're okay - just choose one and take it out of the supporting pokemon pool
|
||||
if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) {
|
||||
this.primaryPokemon = overlap[randSeedInt(overlap.length)];
|
||||
this.secondaryPokemon = this.secondaryPokemon.filter((supp) => supp !== this.primaryPokemon);
|
||||
return true;
|
||||
}
|
||||
console.log("Mystery Encounter Edge Case: Requirement not met due to primay pokemon overlapping with support pokemon. There's no valid primary pokemon left.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Just pick the first qualifying Pokemon
|
||||
this.primaryPokemon = qualified[0];
|
||||
return true;
|
||||
// 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) {
|
||||
this.primaryPokemon = overlap[randSeedInt(overlap.length)];
|
||||
this.secondaryPokemon = this.secondaryPokemon.filter(supp => supp !== this.primaryPokemon);
|
||||
return true;
|
||||
}
|
||||
console.log(
|
||||
"Mystery Encounter Edge Case: Requirement not met due to primay pokemon overlapping with support pokemon. There's no valid primary pokemon left.",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// Just pick the first qualifying Pokemon
|
||||
this.primaryPokemon = qualified[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,12 +196,14 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||
requirements: EncounterSceneRequirement[] = [];
|
||||
primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
|
||||
secondaryPokemonRequirements: EncounterPokemonRequirement[] = [];
|
||||
excludePrimaryFromSecondaryRequirements: boolean = false;
|
||||
isDisabledOnRequirementsNotMet: boolean = true;
|
||||
hasDexProgress: boolean = false;
|
||||
excludePrimaryFromSecondaryRequirements = false;
|
||||
isDisabledOnRequirementsNotMet = true;
|
||||
hasDexProgress = false;
|
||||
dialogue?: OptionTextDisplay;
|
||||
|
||||
static newOptionWithMode(optionMode: MysteryEncounterOptionMode): MysteryEncounterOptionBuilder & Pick<IMysteryEncounterOption, "optionMode"> {
|
||||
static newOptionWithMode(
|
||||
optionMode: MysteryEncounterOptionMode,
|
||||
): MysteryEncounterOptionBuilder & Pick<IMysteryEncounterOption, "optionMode"> {
|
||||
return Object.assign(new MysteryEncounterOptionBuilder(), { optionMode });
|
||||
}
|
||||
|
||||
|
@ -197,7 +215,9 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||
* Adds a {@linkcode EncounterSceneRequirement} to {@linkcode requirements}
|
||||
* @param requirement
|
||||
*/
|
||||
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounterOption, "requirements">> {
|
||||
withSceneRequirement(
|
||||
requirement: EncounterSceneRequirement,
|
||||
): this & Required<Pick<IMysteryEncounterOption, "requirements">> {
|
||||
if (requirement instanceof EncounterPokemonRequirement) {
|
||||
Error("Incorrectly added pokemon requirement as scene requirement.");
|
||||
}
|
||||
|
@ -216,7 +236,9 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||
* If there are scenarios where the Encounter should NOT continue, should return boolean instead of void.
|
||||
* @param onPreOptionPhase
|
||||
*/
|
||||
withPreOptionPhase(onPreOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPreOptionPhase">> {
|
||||
withPreOptionPhase(
|
||||
onPreOptionPhase: OptionPhaseCallback,
|
||||
): this & Required<Pick<IMysteryEncounterOption, "onPreOptionPhase">> {
|
||||
return Object.assign(this, { onPreOptionPhase: onPreOptionPhase });
|
||||
}
|
||||
|
||||
|
@ -228,7 +250,9 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||
return Object.assign(this, { onOptionPhase: onOptionPhase });
|
||||
}
|
||||
|
||||
withPostOptionPhase(onPostOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPostOptionPhase">> {
|
||||
withPostOptionPhase(
|
||||
onPostOptionPhase: OptionPhaseCallback,
|
||||
): this & Required<Pick<IMysteryEncounterOption, "onPostOptionPhase">> {
|
||||
return Object.assign(this, { onPostOptionPhase: onPostOptionPhase });
|
||||
}
|
||||
|
||||
|
@ -236,13 +260,17 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||
* Adds a {@linkcode EncounterPokemonRequirement} to {@linkcode primaryPokemonRequirements}
|
||||
* @param requirement
|
||||
*/
|
||||
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounterOption, "primaryPokemonRequirements">> {
|
||||
withPrimaryPokemonRequirement(
|
||||
requirement: EncounterPokemonRequirement,
|
||||
): this & Required<Pick<IMysteryEncounterOption, "primaryPokemonRequirements">> {
|
||||
if (requirement instanceof EncounterSceneRequirement) {
|
||||
Error("Incorrectly added scene requirement as pokemon requirement.");
|
||||
}
|
||||
|
||||
this.primaryPokemonRequirements.push(requirement);
|
||||
return Object.assign(this, { primaryPokemonRequirements: this.primaryPokemonRequirements });
|
||||
return Object.assign(this, {
|
||||
primaryPokemonRequirements: this.primaryPokemonRequirements,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -254,8 +282,15 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||
* @param invertQuery
|
||||
* @returns
|
||||
*/
|
||||
withPokemonTypeRequirement(type: PokemonType | PokemonType[], excludeFainted?: boolean, minNumberOfPokemon?: number, invertQuery?: boolean) {
|
||||
return this.withPrimaryPokemonRequirement(new TypeRequirement(type, excludeFainted, minNumberOfPokemon, invertQuery));
|
||||
withPokemonTypeRequirement(
|
||||
type: PokemonType | PokemonType[],
|
||||
excludeFainted?: boolean,
|
||||
minNumberOfPokemon?: number,
|
||||
invertQuery?: boolean,
|
||||
) {
|
||||
return this.withPrimaryPokemonRequirement(
|
||||
new TypeRequirement(type, excludeFainted, minNumberOfPokemon, invertQuery),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -274,14 +309,19 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
|||
* @param requirement
|
||||
* @param excludePrimaryFromSecondaryRequirements
|
||||
*/
|
||||
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = true): this & Required<Pick<IMysteryEncounterOption, "secondaryPokemonRequirements">> {
|
||||
withSecondaryPokemonRequirement(
|
||||
requirement: EncounterPokemonRequirement,
|
||||
excludePrimaryFromSecondaryRequirements = true,
|
||||
): this & Required<Pick<IMysteryEncounterOption, "secondaryPokemonRequirements">> {
|
||||
if (requirement instanceof EncounterSceneRequirement) {
|
||||
Error("Incorrectly added scene requirement as pokemon requirement.");
|
||||
}
|
||||
|
||||
this.secondaryPokemonRequirements.push(requirement);
|
||||
this.excludePrimaryFromSecondaryRequirements = excludePrimaryFromSecondaryRequirements;
|
||||
return Object.assign(this, { secondaryPokemonRequirements: this.secondaryPokemonRequirements });
|
||||
return Object.assign(this, {
|
||||
secondaryPokemonRequirements: this.secondaryPokemonRequirements,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,14 @@ import type MysteryEncounterDialogue from "./mystery-encounter-dialogue";
|
|||
import type { OptionPhaseCallback } from "./mystery-encounter-option";
|
||||
import type MysteryEncounterOption from "./mystery-encounter-option";
|
||||
import { MysteryEncounterOptionBuilder } from "./mystery-encounter-option";
|
||||
import { EncounterPokemonRequirement, EncounterSceneRequirement, HealthRatioRequirement, PartySizeRequirement, StatusEffectRequirement, WaveRangeRequirement } from "./mystery-encounter-requirements";
|
||||
import {
|
||||
EncounterPokemonRequirement,
|
||||
EncounterSceneRequirement,
|
||||
HealthRatioRequirement,
|
||||
PartySizeRequirement,
|
||||
StatusEffectRequirement,
|
||||
WaveRangeRequirement,
|
||||
} from "./mystery-encounter-requirements";
|
||||
import type { BattlerIndex } from "#app/battle";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
|
@ -276,9 +283,12 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||
this.encounterTier = this.encounterTier ?? MysteryEncounterTier.COMMON;
|
||||
this.localizationKey = this.localizationKey ?? "";
|
||||
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
|
||||
this.maxAllowedEncounters = this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE ? DEFAULT_MAX_ALLOWED_ROGUE_ENCOUNTERS : DEFAULT_MAX_ALLOWED_ENCOUNTERS;
|
||||
this.maxAllowedEncounters =
|
||||
(this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE)
|
||||
? DEFAULT_MAX_ALLOWED_ROGUE_ENCOUNTERS
|
||||
: DEFAULT_MAX_ALLOWED_ENCOUNTERS;
|
||||
this.encounterMode = MysteryEncounterMode.DEFAULT;
|
||||
this.requirements = this.requirements ? this.requirements : [];
|
||||
this.hideBattleIntroMessage = this.hideBattleIntroMessage ?? false;
|
||||
|
@ -317,7 +327,13 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||
* @param pokemon
|
||||
*/
|
||||
pokemonMeetsPrimaryRequirements(pokemon: Pokemon): boolean {
|
||||
return !this.primaryPokemonRequirements.some(req => !req.queryParty(globalScene.getPlayerParty()).map(p => p.id).includes(pokemon.id));
|
||||
return !this.primaryPokemonRequirements.some(
|
||||
req =>
|
||||
!req
|
||||
.queryParty(globalScene.getPlayerParty())
|
||||
.map(p => p.id)
|
||||
.includes(pokemon.id),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -359,28 +375,27 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||
} else {
|
||||
overlap.push(qp);
|
||||
}
|
||||
|
||||
}
|
||||
if (truePrimaryPool.length > 0) {
|
||||
// Always choose from the non-overlapping pokemon first
|
||||
this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)];
|
||||
return true;
|
||||
} else {
|
||||
// If there are multiple overlapping pokemon, we're okay - just choose one and take it out of the primary pokemon pool
|
||||
if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) {
|
||||
// is this working?
|
||||
this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)];
|
||||
this.secondaryPokemon = this.secondaryPokemon.filter((supp) => supp !== this.primaryPokemon);
|
||||
return true;
|
||||
}
|
||||
console.log("Mystery Encounter Edge Case: Requirement not met due to primary pokemon overlapping with secondary pokemon. There's no valid primary pokemon left.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// this means we CAN have the same pokemon be a primary and secondary pokemon, so just choose any qualifying one randomly.
|
||||
this.primaryPokemon = qualified[Utils.randSeedInt(qualified.length, 0)];
|
||||
return true;
|
||||
// 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) {
|
||||
// is this working?
|
||||
this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)];
|
||||
this.secondaryPokemon = this.secondaryPokemon.filter(supp => supp !== this.primaryPokemon);
|
||||
return true;
|
||||
}
|
||||
console.log(
|
||||
"Mystery Encounter Edge Case: Requirement not met due to primary pokemon overlapping with secondary pokemon. There's no valid primary pokemon left.",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// this means we CAN have the same pokemon be a primary and secondary pokemon, so just choose any qualifying one randomly.
|
||||
this.primaryPokemon = qualified[Utils.randSeedInt(qualified.length, 0)];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -533,28 +548,28 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]];
|
||||
enemyPartyConfigs: EnemyPartyConfig[] = [];
|
||||
|
||||
localizationKey: string = "";
|
||||
localizationKey = "";
|
||||
dialogue: MysteryEncounterDialogue = {};
|
||||
requirements: EncounterSceneRequirement[] = [];
|
||||
primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
|
||||
secondaryPokemonRequirements: EncounterPokemonRequirement[] = [];
|
||||
excludePrimaryFromSupportRequirements: boolean = true;
|
||||
excludePrimaryFromSupportRequirements = true;
|
||||
dialogueTokens: Record<string, string> = {};
|
||||
|
||||
hideBattleIntroMessage: boolean = false;
|
||||
autoHideIntroVisuals: boolean = true;
|
||||
enterIntroVisualsFromRight: boolean = false;
|
||||
continuousEncounter: boolean = false;
|
||||
catchAllowed: boolean = false;
|
||||
fleeAllowed: boolean = true;
|
||||
lockEncounterRewardTiers: boolean = false;
|
||||
startOfBattleEffectsComplete: boolean = false;
|
||||
hasBattleAnimationsWithoutTargets: boolean = false;
|
||||
skipEnemyBattleTurns: boolean = false;
|
||||
skipToFightInput: boolean = false;
|
||||
preventGameStatsUpdates: boolean = false;
|
||||
maxAllowedEncounters: number = 3;
|
||||
expMultiplier: number = 1;
|
||||
hideBattleIntroMessage = false;
|
||||
autoHideIntroVisuals = true;
|
||||
enterIntroVisualsFromRight = false;
|
||||
continuousEncounter = false;
|
||||
catchAllowed = false;
|
||||
fleeAllowed = true;
|
||||
lockEncounterRewardTiers = false;
|
||||
startOfBattleEffectsComplete = false;
|
||||
hasBattleAnimationsWithoutTargets = false;
|
||||
skipEnemyBattleTurns = false;
|
||||
skipToFightInput = false;
|
||||
preventGameStatsUpdates = false;
|
||||
maxAllowedEncounters = 3;
|
||||
expMultiplier = 1;
|
||||
|
||||
/**
|
||||
* REQUIRED
|
||||
|
@ -566,7 +581,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param encounterType
|
||||
* @returns this
|
||||
*/
|
||||
static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & Pick<IMysteryEncounter, "encounterType"> {
|
||||
static withEncounterType(
|
||||
encounterType: MysteryEncounterType,
|
||||
): MysteryEncounterBuilder & Pick<IMysteryEncounter, "encounterType"> {
|
||||
return Object.assign(new MysteryEncounterBuilder(), { encounterType });
|
||||
}
|
||||
|
||||
|
@ -580,12 +597,11 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
*/
|
||||
withOption(option: MysteryEncounterOption): this & Pick<IMysteryEncounter, "options"> {
|
||||
if (!this.options) {
|
||||
const options = [ option ];
|
||||
const options = [option];
|
||||
return Object.assign(this, { options });
|
||||
} else {
|
||||
this.options.push(option);
|
||||
return this;
|
||||
}
|
||||
this.options.push(option);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -598,8 +614,16 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param callback {@linkcode OptionPhaseCallback}
|
||||
* @returns
|
||||
*/
|
||||
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
|
||||
return this.withOption(MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build());
|
||||
withSimpleOption(
|
||||
dialogue: OptionTextDisplay,
|
||||
callback: OptionPhaseCallback,
|
||||
): this & Pick<IMysteryEncounter, "options"> {
|
||||
return this.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue(dialogue)
|
||||
.withOptionPhase(callback)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -612,12 +636,17 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param callback {@linkcode OptionPhaseCallback}
|
||||
* @returns
|
||||
*/
|
||||
withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
|
||||
return this.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue(dialogue)
|
||||
.withOptionPhase(callback).build());
|
||||
withSimpleDexProgressOption(
|
||||
dialogue: OptionTextDisplay,
|
||||
callback: OptionPhaseCallback,
|
||||
): this & Pick<IMysteryEncounter, "options"> {
|
||||
return this.withOption(
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue(dialogue)
|
||||
.withOptionPhase(callback)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -626,7 +655,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param spriteConfigs
|
||||
* @returns
|
||||
*/
|
||||
withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick<IMysteryEncounter, "spriteConfigs"> {
|
||||
withIntroSpriteConfigs(
|
||||
spriteConfigs: MysteryEncounterSpriteConfig[],
|
||||
): this & Pick<IMysteryEncounter, "spriteConfigs"> {
|
||||
return Object.assign(this, { spriteConfigs: spriteConfigs });
|
||||
}
|
||||
|
||||
|
@ -635,7 +666,13 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
return this;
|
||||
}
|
||||
|
||||
withIntro({ spriteConfigs, dialogue } : {spriteConfigs: MysteryEncounterSpriteConfig[], dialogue?: MysteryEncounterDialogue["intro"]}) {
|
||||
withIntro({
|
||||
spriteConfigs,
|
||||
dialogue,
|
||||
}: {
|
||||
spriteConfigs: MysteryEncounterSpriteConfig[];
|
||||
dialogue?: MysteryEncounterDialogue["intro"];
|
||||
}) {
|
||||
return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue);
|
||||
}
|
||||
|
||||
|
@ -676,8 +713,10 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param encounterAnimations
|
||||
* @returns
|
||||
*/
|
||||
withAnimations(...encounterAnimations: EncounterAnim[]): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
|
||||
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [ encounterAnimations ];
|
||||
withAnimations(
|
||||
...encounterAnimations: EncounterAnim[]
|
||||
): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
|
||||
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
|
||||
return Object.assign(this, { encounterAnimations: animations });
|
||||
}
|
||||
|
||||
|
@ -686,8 +725,10 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @returns
|
||||
* @param disallowedGameModes
|
||||
*/
|
||||
withDisallowedGameModes(...disallowedGameModes: GameModes[]): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
|
||||
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [ disallowedGameModes ];
|
||||
withDisallowedGameModes(
|
||||
...disallowedGameModes: GameModes[]
|
||||
): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
|
||||
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes];
|
||||
return Object.assign(this, { disallowedGameModes: gameModes });
|
||||
}
|
||||
|
||||
|
@ -696,8 +737,10 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @returns
|
||||
* @param disallowedChallenges
|
||||
*/
|
||||
withDisallowedChallenges(...disallowedChallenges: Challenges[]): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
|
||||
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [ disallowedChallenges ];
|
||||
withDisallowedChallenges(
|
||||
...disallowedChallenges: Challenges[]
|
||||
): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
|
||||
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges];
|
||||
return Object.assign(this, { disallowedChallenges: challenges });
|
||||
}
|
||||
|
||||
|
@ -707,7 +750,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* Default false
|
||||
* @param continuousEncounter
|
||||
*/
|
||||
withContinuousEncounter(continuousEncounter: boolean): this & Required<Pick<IMysteryEncounter, "continuousEncounter">> {
|
||||
withContinuousEncounter(
|
||||
continuousEncounter: boolean,
|
||||
): this & Required<Pick<IMysteryEncounter, "continuousEncounter">> {
|
||||
return Object.assign(this, { continuousEncounter: continuousEncounter });
|
||||
}
|
||||
|
||||
|
@ -717,7 +762,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* Default false
|
||||
* @param hasBattleAnimationsWithoutTargets
|
||||
*/
|
||||
withBattleAnimationsWithoutTargets(hasBattleAnimationsWithoutTargets: boolean): this & Required<Pick<IMysteryEncounter, "hasBattleAnimationsWithoutTargets">> {
|
||||
withBattleAnimationsWithoutTargets(
|
||||
hasBattleAnimationsWithoutTargets: boolean,
|
||||
): this & Required<Pick<IMysteryEncounter, "hasBattleAnimationsWithoutTargets">> {
|
||||
return Object.assign(this, { hasBattleAnimationsWithoutTargets });
|
||||
}
|
||||
|
||||
|
@ -727,7 +774,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* Default false
|
||||
* @param skipEnemyBattleTurns
|
||||
*/
|
||||
withSkipEnemyBattleTurns(skipEnemyBattleTurns: boolean): this & Required<Pick<IMysteryEncounter, "skipEnemyBattleTurns">> {
|
||||
withSkipEnemyBattleTurns(
|
||||
skipEnemyBattleTurns: boolean,
|
||||
): this & Required<Pick<IMysteryEncounter, "skipEnemyBattleTurns">> {
|
||||
return Object.assign(this, { skipEnemyBattleTurns });
|
||||
}
|
||||
|
||||
|
@ -744,7 +793,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* If true, will prevent updating {@linkcode GameStats} for encountering and/or defeating Pokemon
|
||||
* Default `false`
|
||||
*/
|
||||
withPreventGameStatsUpdates(preventGameStatsUpdates: boolean): this & Required<Pick<IMysteryEncounter, "preventGameStatsUpdates">> {
|
||||
withPreventGameStatsUpdates(
|
||||
preventGameStatsUpdates: boolean,
|
||||
): this & Required<Pick<IMysteryEncounter, "preventGameStatsUpdates">> {
|
||||
return Object.assign(this, { preventGameStatsUpdates });
|
||||
}
|
||||
|
||||
|
@ -753,7 +804,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param maxAllowedEncounters
|
||||
* @returns
|
||||
*/
|
||||
withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required<Pick<IMysteryEncounter, "maxAllowedEncounters">> {
|
||||
withMaxAllowedEncounters(
|
||||
maxAllowedEncounters: number,
|
||||
): this & Required<Pick<IMysteryEncounter, "maxAllowedEncounters">> {
|
||||
return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters });
|
||||
}
|
||||
|
||||
|
@ -764,7 +817,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param requirement
|
||||
* @returns
|
||||
*/
|
||||
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
withSceneRequirement(
|
||||
requirement: EncounterSceneRequirement,
|
||||
): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
if (requirement instanceof EncounterPokemonRequirement) {
|
||||
Error("Incorrectly added pokemon requirement as scene requirement.");
|
||||
}
|
||||
|
@ -780,7 +835,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @returns
|
||||
*/
|
||||
withSceneWaveRangeRequirement(min: number, max?: number): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
return this.withSceneRequirement(new WaveRangeRequirement([ min, max ?? min ]));
|
||||
return this.withSceneRequirement(new WaveRangeRequirement([min, max ?? min]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -791,8 +846,12 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param excludeDisallowedPokemon if true, only counts allowed (legal in Challenge/unfainted) mons
|
||||
* @returns
|
||||
*/
|
||||
withScenePartySizeRequirement(min: number, max?: number, excludeDisallowedPokemon: boolean = false): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
return this.withSceneRequirement(new PartySizeRequirement([ min, max ?? min ], excludeDisallowedPokemon));
|
||||
withScenePartySizeRequirement(
|
||||
min: number,
|
||||
max?: number,
|
||||
excludeDisallowedPokemon = false,
|
||||
): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeDisallowedPokemon));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -801,13 +860,17 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param requirement {@linkcode EncounterPokemonRequirement}
|
||||
* @returns
|
||||
*/
|
||||
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
|
||||
withPrimaryPokemonRequirement(
|
||||
requirement: EncounterPokemonRequirement,
|
||||
): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
|
||||
if (requirement instanceof EncounterSceneRequirement) {
|
||||
Error("Incorrectly added scene requirement as pokemon requirement.");
|
||||
}
|
||||
|
||||
this.primaryPokemonRequirements.push(requirement);
|
||||
return Object.assign(this, { primaryPokemonRequirements: this.primaryPokemonRequirements });
|
||||
return Object.assign(this, {
|
||||
primaryPokemonRequirements: this.primaryPokemonRequirements,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -818,8 +881,14 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param invertQuery if true will invert the query
|
||||
* @returns
|
||||
*/
|
||||
withPrimaryPokemonStatusEffectRequirement(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
|
||||
return this.withPrimaryPokemonRequirement(new StatusEffectRequirement(statusEffect, minNumberOfPokemon, invertQuery));
|
||||
withPrimaryPokemonStatusEffectRequirement(
|
||||
statusEffect: StatusEffect | StatusEffect[],
|
||||
minNumberOfPokemon = 1,
|
||||
invertQuery = false,
|
||||
): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
|
||||
return this.withPrimaryPokemonRequirement(
|
||||
new StatusEffectRequirement(statusEffect, minNumberOfPokemon, invertQuery),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -830,21 +899,33 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param invertQuery if true will invert the query
|
||||
* @returns
|
||||
*/
|
||||
withPrimaryPokemonHealthRatioRequirement(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
|
||||
return this.withPrimaryPokemonRequirement(new HealthRatioRequirement(requiredHealthRange, minNumberOfPokemon, invertQuery));
|
||||
withPrimaryPokemonHealthRatioRequirement(
|
||||
requiredHealthRange: [number, number],
|
||||
minNumberOfPokemon = 1,
|
||||
invertQuery = false,
|
||||
): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
|
||||
return this.withPrimaryPokemonRequirement(
|
||||
new HealthRatioRequirement(requiredHealthRange, minNumberOfPokemon, invertQuery),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Maybe add an optional parameter for excluding primary pokemon from the support cast?
|
||||
// ex. if your only grass type pokemon, a snivy, is chosen as primary, if the support pokemon requires a grass type, the event won't trigger because
|
||||
// it's already been
|
||||
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = false): this & Required<Pick<IMysteryEncounter, "secondaryPokemonRequirements">> {
|
||||
withSecondaryPokemonRequirement(
|
||||
requirement: EncounterPokemonRequirement,
|
||||
excludePrimaryFromSecondaryRequirements = false,
|
||||
): this & Required<Pick<IMysteryEncounter, "secondaryPokemonRequirements">> {
|
||||
if (requirement instanceof EncounterSceneRequirement) {
|
||||
Error("Incorrectly added scene requirement as pokemon requirement.");
|
||||
}
|
||||
|
||||
this.secondaryPokemonRequirements.push(requirement);
|
||||
this.excludePrimaryFromSupportRequirements = excludePrimaryFromSecondaryRequirements;
|
||||
return Object.assign(this, { excludePrimaryFromSecondaryRequirements: this.excludePrimaryFromSupportRequirements, secondaryPokemonRequirements: this.secondaryPokemonRequirements });
|
||||
return Object.assign(this, {
|
||||
excludePrimaryFromSecondaryRequirements: this.excludePrimaryFromSupportRequirements,
|
||||
secondaryPokemonRequirements: this.secondaryPokemonRequirements,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -919,15 +1000,21 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* @param hideBattleIntroMessage If `true`, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter
|
||||
* @returns
|
||||
*/
|
||||
withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required<Pick<IMysteryEncounter, "hideBattleIntroMessage">> {
|
||||
return Object.assign(this, { hideBattleIntroMessage: hideBattleIntroMessage });
|
||||
withHideWildIntroMessage(
|
||||
hideBattleIntroMessage: boolean,
|
||||
): this & Required<Pick<IMysteryEncounter, "hideBattleIntroMessage">> {
|
||||
return Object.assign(this, {
|
||||
hideBattleIntroMessage: hideBattleIntroMessage,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param autoHideIntroVisuals If `false`, will not hide the intro visuals that are displayed at the beginning of encounter
|
||||
* @returns
|
||||
*/
|
||||
withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required<Pick<IMysteryEncounter, "autoHideIntroVisuals">> {
|
||||
withAutoHideIntroVisuals(
|
||||
autoHideIntroVisuals: boolean,
|
||||
): this & Required<Pick<IMysteryEncounter, "autoHideIntroVisuals">> {
|
||||
return Object.assign(this, { autoHideIntroVisuals: autoHideIntroVisuals });
|
||||
}
|
||||
|
||||
|
@ -936,8 +1023,12 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
* Default false
|
||||
* @returns
|
||||
*/
|
||||
withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required<Pick<IMysteryEncounter, "enterIntroVisualsFromRight">> {
|
||||
return Object.assign(this, { enterIntroVisualsFromRight: enterIntroVisualsFromRight });
|
||||
withEnterIntroVisualsFromRight(
|
||||
enterIntroVisualsFromRight: boolean,
|
||||
): this & Required<Pick<IMysteryEncounter, "enterIntroVisualsFromRight">> {
|
||||
return Object.assign(this, {
|
||||
enterIntroVisualsFromRight: enterIntroVisualsFromRight,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -954,7 +1045,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
encounterOptionsDialogue: {
|
||||
...encounterOptionsDialogue,
|
||||
title,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return this;
|
||||
|
@ -974,7 +1065,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
encounterOptionsDialogue: {
|
||||
...encounterOptionsDialogue,
|
||||
description,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return this;
|
||||
|
@ -994,7 +1085,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||
encounterOptionsDialogue: {
|
||||
...encounterOptionsDialogue,
|
||||
query,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return this;
|
||||
|
|
|
@ -80,7 +80,7 @@ export const EXTREME_ENCOUNTER_BIOMES = [
|
|||
Biome.WASTELAND,
|
||||
Biome.ABYSS,
|
||||
Biome.SPACE,
|
||||
Biome.END
|
||||
Biome.END,
|
||||
];
|
||||
|
||||
export const NON_EXTREME_ENCOUNTER_BIOMES = [
|
||||
|
@ -108,7 +108,7 @@ export const NON_EXTREME_ENCOUNTER_BIOMES = [
|
|||
Biome.SLUM,
|
||||
Biome.SNOWY_FOREST,
|
||||
Biome.ISLAND,
|
||||
Biome.LABORATORY
|
||||
Biome.LABORATORY,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -147,7 +147,7 @@ export const HUMAN_TRANSITABLE_BIOMES = [
|
|||
Biome.SLUM,
|
||||
Biome.SNOWY_FOREST,
|
||||
Biome.ISLAND,
|
||||
Biome.LABORATORY
|
||||
Biome.LABORATORY,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -168,11 +168,12 @@ export const CIVILIZATION_ENCOUNTER_BIOMES = [
|
|||
Biome.FACTORY,
|
||||
Biome.CONSTRUCTION_SITE,
|
||||
Biome.SLUM,
|
||||
Biome.ISLAND
|
||||
Biome.ISLAND,
|
||||
];
|
||||
|
||||
export const allMysteryEncounters: { [encounterType: number]: MysteryEncounter } = {};
|
||||
|
||||
export const allMysteryEncounters: {
|
||||
[encounterType: number]: MysteryEncounter;
|
||||
} = {};
|
||||
|
||||
const extremeBiomeEncounters: MysteryEncounterType[] = [];
|
||||
|
||||
|
@ -187,14 +188,14 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
|
|||
MysteryEncounterType.THE_POKEMON_SALESMAN,
|
||||
// MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, Disabled
|
||||
MysteryEncounterType.THE_WINSTRATE_CHALLENGE,
|
||||
MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER
|
||||
MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER,
|
||||
];
|
||||
|
||||
const civilizationBiomeEncounters: MysteryEncounterType[] = [
|
||||
MysteryEncounterType.DEPARTMENT_STORE_SALE,
|
||||
MysteryEncounterType.PART_TIMER,
|
||||
MysteryEncounterType.FUN_AND_GAMES,
|
||||
MysteryEncounterType.GLOBAL_TRADE_SYSTEM
|
||||
MysteryEncounterType.GLOBAL_TRADE_SYSTEM,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -213,7 +214,7 @@ const anyBiomeEncounters: MysteryEncounterType[] = [
|
|||
MysteryEncounterType.WEIRD_DREAM,
|
||||
MysteryEncounterType.TELEPORTING_HIJINKS,
|
||||
MysteryEncounterType.BUG_TYPE_SUPERFAN,
|
||||
MysteryEncounterType.UNCOMMON_BREED
|
||||
MysteryEncounterType.UNCOMMON_BREED,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -224,72 +225,40 @@ const anyBiomeEncounters: MysteryEncounterType[] = [
|
|||
* that biome groups do not cover
|
||||
*/
|
||||
export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
|
||||
[ Biome.TOWN, []],
|
||||
[ Biome.PLAINS, [
|
||||
MysteryEncounterType.SLUMBERING_SNORLAX,
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
[ Biome.GRASS, [
|
||||
MysteryEncounterType.SLUMBERING_SNORLAX,
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
[ Biome.TALL_GRASS, [
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
[ Biome.METROPOLIS, []],
|
||||
[ Biome.FOREST, [
|
||||
MysteryEncounterType.SAFARI_ZONE,
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
[ Biome.SEA, [
|
||||
MysteryEncounterType.LOST_AT_SEA
|
||||
]],
|
||||
[ Biome.SWAMP, [
|
||||
MysteryEncounterType.SAFARI_ZONE
|
||||
]],
|
||||
[ Biome.BEACH, []],
|
||||
[ Biome.LAKE, []],
|
||||
[ Biome.SEABED, []],
|
||||
[ Biome.MOUNTAIN, []],
|
||||
[ Biome.BADLANDS, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[ Biome.CAVE, [
|
||||
MysteryEncounterType.THE_STRONG_STUFF
|
||||
]],
|
||||
[ Biome.DESERT, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[ Biome.ICE_CAVE, []],
|
||||
[ Biome.MEADOW, []],
|
||||
[ Biome.POWER_PLANT, []],
|
||||
[ Biome.VOLCANO, [
|
||||
MysteryEncounterType.FIERY_FALLOUT,
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[ Biome.GRAVEYARD, []],
|
||||
[ Biome.DOJO, []],
|
||||
[ Biome.FACTORY, []],
|
||||
[ Biome.RUINS, []],
|
||||
[ Biome.WASTELAND, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[ Biome.ABYSS, [
|
||||
MysteryEncounterType.DANCING_LESSONS
|
||||
]],
|
||||
[ Biome.SPACE, [
|
||||
MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER
|
||||
]],
|
||||
[ Biome.CONSTRUCTION_SITE, []],
|
||||
[ Biome.JUNGLE, [
|
||||
MysteryEncounterType.SAFARI_ZONE
|
||||
]],
|
||||
[ Biome.FAIRY_CAVE, []],
|
||||
[ Biome.TEMPLE, []],
|
||||
[ Biome.SLUM, []],
|
||||
[ Biome.SNOWY_FOREST, []],
|
||||
[ Biome.ISLAND, []],
|
||||
[ Biome.LABORATORY, []]
|
||||
[Biome.TOWN, []],
|
||||
[Biome.PLAINS, [MysteryEncounterType.SLUMBERING_SNORLAX, MysteryEncounterType.ABSOLUTE_AVARICE]],
|
||||
[Biome.GRASS, [MysteryEncounterType.SLUMBERING_SNORLAX, MysteryEncounterType.ABSOLUTE_AVARICE]],
|
||||
[Biome.TALL_GRASS, [MysteryEncounterType.ABSOLUTE_AVARICE]],
|
||||
[Biome.METROPOLIS, []],
|
||||
[Biome.FOREST, [MysteryEncounterType.SAFARI_ZONE, MysteryEncounterType.ABSOLUTE_AVARICE]],
|
||||
[Biome.SEA, [MysteryEncounterType.LOST_AT_SEA]],
|
||||
[Biome.SWAMP, [MysteryEncounterType.SAFARI_ZONE]],
|
||||
[Biome.BEACH, []],
|
||||
[Biome.LAKE, []],
|
||||
[Biome.SEABED, []],
|
||||
[Biome.MOUNTAIN, []],
|
||||
[Biome.BADLANDS, [MysteryEncounterType.DANCING_LESSONS]],
|
||||
[Biome.CAVE, [MysteryEncounterType.THE_STRONG_STUFF]],
|
||||
[Biome.DESERT, [MysteryEncounterType.DANCING_LESSONS]],
|
||||
[Biome.ICE_CAVE, []],
|
||||
[Biome.MEADOW, []],
|
||||
[Biome.POWER_PLANT, []],
|
||||
[Biome.VOLCANO, [MysteryEncounterType.FIERY_FALLOUT, MysteryEncounterType.DANCING_LESSONS]],
|
||||
[Biome.GRAVEYARD, []],
|
||||
[Biome.DOJO, []],
|
||||
[Biome.FACTORY, []],
|
||||
[Biome.RUINS, []],
|
||||
[Biome.WASTELAND, [MysteryEncounterType.DANCING_LESSONS]],
|
||||
[Biome.ABYSS, [MysteryEncounterType.DANCING_LESSONS]],
|
||||
[Biome.SPACE, [MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER]],
|
||||
[Biome.CONSTRUCTION_SITE, []],
|
||||
[Biome.JUNGLE, [MysteryEncounterType.SAFARI_ZONE]],
|
||||
[Biome.FAIRY_CAVE, []],
|
||||
[Biome.TEMPLE, []],
|
||||
[Biome.SLUM, []],
|
||||
[Biome.SNOWY_FOREST, []],
|
||||
[Biome.ISLAND, []],
|
||||
[Biome.LABORATORY, []],
|
||||
]);
|
||||
|
||||
export function initMysteryEncounters() {
|
||||
|
@ -364,7 +333,7 @@ export function initMysteryEncounters() {
|
|||
|
||||
// Add ANY biome encounters to biome map
|
||||
// eslint-disable-next-line
|
||||
let encounterBiomeTableLog = "";
|
||||
let _encounterBiomeTableLog = "";
|
||||
mysteryEncountersByBiome.forEach((biomeEncounters, biome) => {
|
||||
anyBiomeEncounters.forEach(encounter => {
|
||||
if (!biomeEncounters.includes(encounter)) {
|
||||
|
@ -372,7 +341,10 @@ export function initMysteryEncounters() {
|
|||
}
|
||||
});
|
||||
|
||||
encounterBiomeTableLog += `${getBiomeName(biome).toUpperCase()}: [${biomeEncounters.map(type => MysteryEncounterType[type].toString().toLowerCase()).sort().join(", ")}]\n`;
|
||||
_encounterBiomeTableLog += `${getBiomeName(biome).toUpperCase()}: [${biomeEncounters
|
||||
.map(type => MysteryEncounterType[type].toString().toLowerCase())
|
||||
.sort()
|
||||
.join(", ")}]\n`;
|
||||
});
|
||||
|
||||
//console.debug("All Mystery Encounters by Biome:\n" + encounterBiomeTableLog);
|
||||
|
|
|
@ -29,7 +29,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
|
|||
|
||||
constructor(requiredMoves: Moves | Moves[], options: CanLearnMoveRequirementOptions = {}) {
|
||||
super();
|
||||
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [ requiredMoves ];
|
||||
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves];
|
||||
|
||||
this.excludeLevelMoves = options.excludeLevelMoves ?? false;
|
||||
this.excludeTmMoves = options.excludeTmMoves ?? false;
|
||||
|
@ -40,7 +40,9 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
|
|||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
const partyPokemon = globalScene.getPlayerParty().filter((pkm) => (this.includeFainted ? pkm.isAllowedInChallenge() : pkm.isAllowedInBattle()));
|
||||
const partyPokemon = globalScene
|
||||
.getPlayerParty()
|
||||
.filter(pkm => (this.includeFainted ? pkm.isAllowedInChallenge() : pkm.isAllowedInBattle()));
|
||||
|
||||
if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
|
||||
return false;
|
||||
|
@ -51,25 +53,24 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
|
|||
|
||||
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) =>
|
||||
return partyPokemon.filter(pokemon =>
|
||||
// every required move should be included
|
||||
this.requiredMoves.every((requiredMove) => this.getAllPokemonMoves(pokemon).includes(requiredMove))
|
||||
);
|
||||
} else {
|
||||
return partyPokemon.filter(
|
||||
(pokemon) =>
|
||||
// none of the "required" moves should be included
|
||||
!this.requiredMoves.some((requiredMove) => this.getAllPokemonMoves(pokemon).includes(requiredMove))
|
||||
this.requiredMoves.every(requiredMove => this.getAllPokemonMoves(pokemon).includes(requiredMove)),
|
||||
);
|
||||
}
|
||||
return partyPokemon.filter(
|
||||
pokemon =>
|
||||
// none of the "required" moves should be included
|
||||
!this.requiredMoves.some(requiredMove => this.getAllPokemonMoves(pokemon).includes(requiredMove)),
|
||||
);
|
||||
}
|
||||
|
||||
override getDialogueToken(__pokemon?: PlayerPokemon): [string, string] {
|
||||
return [ "requiredMoves", this.requiredMoves.map(m => new PokemonMove(m).getName()).join(", ") ];
|
||||
return ["requiredMoves", this.requiredMoves.map(m => new PokemonMove(m).getName()).join(", ")];
|
||||
}
|
||||
|
||||
private getPokemonLevelMoves(pkm: PlayerPokemon): Moves[] {
|
||||
return pkm.getLevelMoves().map(([ _level, move ]) => move);
|
||||
return pkm.getLevelMoves().map(([_level, move]) => move);
|
||||
}
|
||||
|
||||
private getAllPokemonMoves(pkm: PlayerPokemon): Moves[] {
|
||||
|
|
|
@ -4,14 +4,7 @@ import { Abilities } from "#enums/abilities";
|
|||
/**
|
||||
* Moves that "steal" things
|
||||
*/
|
||||
export const STEALING_MOVES = [
|
||||
Moves.PLUCK,
|
||||
Moves.COVET,
|
||||
Moves.KNOCK_OFF,
|
||||
Moves.THIEF,
|
||||
Moves.TRICK,
|
||||
Moves.SWITCHEROO
|
||||
];
|
||||
export const STEALING_MOVES = [Moves.PLUCK, Moves.COVET, Moves.KNOCK_OFF, Moves.THIEF, Moves.TRICK, Moves.SWITCHEROO];
|
||||
|
||||
/**
|
||||
* Moves that "charm" someone
|
||||
|
@ -24,7 +17,7 @@ export const CHARMING_MOVES = [
|
|||
Moves.ATTRACT,
|
||||
Moves.SWEET_SCENT,
|
||||
Moves.CAPTIVATE,
|
||||
Moves.AROMATIC_MIST
|
||||
Moves.AROMATIC_MIST,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -42,7 +35,7 @@ export const DANCING_MOVES = [
|
|||
Moves.QUIVER_DANCE,
|
||||
Moves.SWORDS_DANCE,
|
||||
Moves.TEETER_DANCE,
|
||||
Moves.VICTORY_DANCE
|
||||
Moves.VICTORY_DANCE,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -60,7 +53,7 @@ export const DISTRACTION_MOVES = [
|
|||
Moves.CAPTIVATE,
|
||||
Moves.RAGE_POWDER,
|
||||
Moves.SUBSTITUTE,
|
||||
Moves.SHED_TAIL
|
||||
Moves.SHED_TAIL,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -79,7 +72,7 @@ export const PROTECTING_MOVES = [
|
|||
Moves.CRAFTY_SHIELD,
|
||||
Moves.SPIKY_SHIELD,
|
||||
Moves.OBSTRUCT,
|
||||
Moves.DETECT
|
||||
Moves.DETECT,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -116,7 +109,7 @@ export const EXTORTION_ABILITIES = [
|
|||
Abilities.ARENA_TRAP,
|
||||
Abilities.SHADOW_TAG,
|
||||
Abilities.SUCTION_CUPS,
|
||||
Abilities.STICKY_HOLD
|
||||
Abilities.STICKY_HOLD,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -133,5 +126,5 @@ export const FIRE_RESISTANT_ABILITIES = [
|
|||
Abilities.MAGMA_ARMOR,
|
||||
Abilities.WATER_VEIL,
|
||||
Abilities.STEAM_ENGINE,
|
||||
Abilities.PRIMORDIAL_SEA
|
||||
Abilities.PRIMORDIAL_SEA,
|
||||
];
|
||||
|
|
|
@ -63,7 +63,13 @@ export function queueEncounterMessage(contentKey: string): void {
|
|||
* @param callbackDelay
|
||||
* @param promptDelay
|
||||
*/
|
||||
export function showEncounterText(contentKey: string, delay: number | null = null, callbackDelay: number = 0, prompt: boolean = true, promptDelay: number | null = null): Promise<void> {
|
||||
export function showEncounterText(
|
||||
contentKey: string,
|
||||
delay: number | null = null,
|
||||
callbackDelay = 0,
|
||||
prompt = true,
|
||||
promptDelay: number | null = null,
|
||||
): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
const text: string | null = getEncounterText(contentKey);
|
||||
globalScene.ui.showText(text ?? "", delay, () => resolve(), callbackDelay, prompt, promptDelay);
|
||||
|
@ -78,7 +84,12 @@ export function showEncounterText(contentKey: string, delay: number | null = nul
|
|||
* @param speakerContentKey
|
||||
* @param callbackDelay
|
||||
*/
|
||||
export function showEncounterDialogue(textContentKey: string, speakerContentKey: string, delay: number | null = null, callbackDelay: number = 0): Promise<void> {
|
||||
export function showEncounterDialogue(
|
||||
textContentKey: string,
|
||||
speakerContentKey: string,
|
||||
delay: number | null = null,
|
||||
callbackDelay = 0,
|
||||
): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
const text: string | null = getEncounterText(textContentKey);
|
||||
const speaker: string | null = getEncounterText(speakerContentKey);
|
||||
|
|
|
@ -2,14 +2,29 @@ import type Battle from "#app/battle";
|
|||
import { BattlerIndex, BattleType } from "#app/battle";
|
||||
import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes";
|
||||
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import {
|
||||
AVERAGE_ENCOUNTERS_PER_RUN_TARGET,
|
||||
WEIGHT_INCREMENT_ON_SPAWN_MISS,
|
||||
} from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import type { AiType, PlayerPokemon } from "#app/field/pokemon";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { EnemyPokemon, FieldPosition, PokemonMove, PokemonSummonData } from "#app/field/pokemon";
|
||||
import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type";
|
||||
import { getPartyLuckValue, ModifierPoolType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases";
|
||||
import {
|
||||
getPartyLuckValue,
|
||||
ModifierPoolType,
|
||||
ModifierTypeGenerator,
|
||||
ModifierTypeOption,
|
||||
modifierTypes,
|
||||
regenerateModifierPoolThresholds,
|
||||
} from "#app/modifier/modifier-type";
|
||||
import {
|
||||
MysteryEncounterBattlePhase,
|
||||
MysteryEncounterBattleStartCleanupPhase,
|
||||
MysteryEncounterPhase,
|
||||
MysteryEncounterRewardsPhase,
|
||||
} from "#app/phases/mystery-encounter-phases";
|
||||
import type PokemonData from "#app/system/pokemon-data";
|
||||
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
|
||||
|
@ -71,7 +86,7 @@ export function doTrainerExclamation() {
|
|||
globalScene.time.delayedCall(800, () => {
|
||||
globalScene.field.remove(exclamationSprite, true);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
globalScene.playSound("battle_anims/GEN8- Exclaim", { volume: 0.7 });
|
||||
|
@ -151,8 +166,15 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
|
||||
const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle);
|
||||
doubleBattle = doubleTrainer;
|
||||
const trainerFemale = isNullOrUndefined(partyConfig.female) ? !!(Utils.randSeedInt(2)) : partyConfig.female;
|
||||
const newTrainer = new Trainer(trainerConfig.trainerType, doubleTrainer ? TrainerVariant.DOUBLE : trainerFemale ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, undefined, undefined, undefined, trainerConfig);
|
||||
const trainerFemale = isNullOrUndefined(partyConfig.female) ? !!Utils.randSeedInt(2) : partyConfig.female;
|
||||
const newTrainer = new Trainer(
|
||||
trainerConfig.trainerType,
|
||||
doubleTrainer ? TrainerVariant.DOUBLE : trainerFemale ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
trainerConfig,
|
||||
);
|
||||
newTrainer.x += 300;
|
||||
newTrainer.setVisible(false);
|
||||
globalScene.field.add(newTrainer);
|
||||
|
@ -163,7 +185,12 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
} else {
|
||||
// Wild
|
||||
globalScene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.WILD_BATTLE;
|
||||
const numEnemies = partyConfig?.pokemonConfigs && partyConfig.pokemonConfigs.length > 0 ? partyConfig?.pokemonConfigs?.length : doubleBattle ? 2 : 1;
|
||||
const numEnemies =
|
||||
partyConfig?.pokemonConfigs && partyConfig.pokemonConfigs.length > 0
|
||||
? partyConfig?.pokemonConfigs?.length
|
||||
: doubleBattle
|
||||
? 2
|
||||
: 1;
|
||||
battle.enemyLevels = new Array(numEnemies).fill(null).map(() => globalScene.currentBattle.getLevelForWave());
|
||||
}
|
||||
|
||||
|
@ -183,8 +210,8 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
battle.enemyLevels = battle.enemyLevels.map(level => level + additive);
|
||||
|
||||
battle.enemyLevels.forEach((level, e) => {
|
||||
let enemySpecies;
|
||||
let dataSource;
|
||||
let enemySpecies: PokemonSpecies | undefined;
|
||||
let dataSource: PokemonData | undefined;
|
||||
let isBoss = false;
|
||||
if (!loaded) {
|
||||
if ((!isNullOrUndefined(trainerType) || trainerConfig) && battle.trainer) {
|
||||
|
@ -195,7 +222,14 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
dataSource = config.dataSource;
|
||||
enemySpecies = config.species;
|
||||
isBoss = config.isBoss;
|
||||
battle.enemyParty[e] = globalScene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, false, dataSource);
|
||||
battle.enemyParty[e] = globalScene.addEnemyPokemon(
|
||||
enemySpecies,
|
||||
level,
|
||||
TrainerSlot.TRAINER,
|
||||
isBoss,
|
||||
false,
|
||||
dataSource,
|
||||
);
|
||||
} else {
|
||||
battle.enemyParty[e] = battle.trainer.genPartyMember(e);
|
||||
}
|
||||
|
@ -213,7 +247,14 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
enemySpecies = globalScene.randomSpecies(battle.waveIndex, level, true);
|
||||
}
|
||||
|
||||
battle.enemyParty[e] = globalScene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, false, dataSource);
|
||||
battle.enemyParty[e] = globalScene.addEnemyPokemon(
|
||||
enemySpecies,
|
||||
level,
|
||||
TrainerSlot.NONE,
|
||||
isBoss,
|
||||
false,
|
||||
dataSource,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,7 +270,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
enemyPokemon.resetSummonData();
|
||||
}
|
||||
|
||||
if (!loaded && isNullOrUndefined(partyConfig.countAsSeen) || partyConfig.countAsSeen) {
|
||||
if ((!loaded && isNullOrUndefined(partyConfig.countAsSeen)) || partyConfig.countAsSeen) {
|
||||
globalScene.gameData.setPokemonSeen(enemyPokemon, true, !!(trainerType || trainerConfig));
|
||||
}
|
||||
|
||||
|
@ -268,7 +309,9 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
|
||||
// Set Boss
|
||||
if (config.isBoss) {
|
||||
let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments! : globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, level, enemySpecies, true);
|
||||
let segments = !isNullOrUndefined(config.bossSegments)
|
||||
? config.bossSegments!
|
||||
: globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, level, enemySpecies, true);
|
||||
if (!isNullOrUndefined(config.bossSegmentModifier)) {
|
||||
segments += config.bossSegmentModifier;
|
||||
}
|
||||
|
@ -295,7 +338,11 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
if (statusEffects) {
|
||||
// Default to cureturn 3 for sleep
|
||||
const status = Array.isArray(statusEffects) ? statusEffects[0] : statusEffects;
|
||||
const cureTurn = Array.isArray(statusEffects) ? statusEffects[1] : statusEffects === StatusEffect.SLEEP ? 3 : undefined;
|
||||
const cureTurn = Array.isArray(statusEffects)
|
||||
? statusEffects[1]
|
||||
: statusEffects === StatusEffect.SLEEP
|
||||
? 3
|
||||
: undefined;
|
||||
enemyPokemon.status = new Status(status, 0, cureTurn);
|
||||
}
|
||||
|
||||
|
@ -368,7 +415,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
` Spd: ${enemyPokemon.stats[5]} (${enemyPokemon.ivs[5]})`,
|
||||
];
|
||||
const moveset: string[] = [];
|
||||
enemyPokemon.getMoveset().forEach((move) => {
|
||||
enemyPokemon.getMoveset().forEach(move => {
|
||||
moveset.push(move!.getName()); // TODO: remove `!` after moveset-null removal PR
|
||||
});
|
||||
|
||||
|
@ -381,7 +428,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
console.log(
|
||||
`Ability: ${enemyPokemon.getAbility().name}`,
|
||||
`| Passive Ability${enemyPokemon.hasPassive() ? "" : " (inactive)"}: ${enemyPokemon.getPassiveAbility().name}`,
|
||||
`${enemyPokemon.isBoss() ? `| Boss Bars: ${enemyPokemon.bossSegments}` : ""}`
|
||||
`${enemyPokemon.isBoss() ? `| Boss Bars: ${enemyPokemon.bossSegments}` : ""}`,
|
||||
);
|
||||
console.log("Moveset:", moveset);
|
||||
});
|
||||
|
@ -400,7 +447,10 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
}
|
||||
});
|
||||
if (!loaded) {
|
||||
regenerateModifierPoolThresholds(globalScene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD);
|
||||
regenerateModifierPoolThresholds(
|
||||
globalScene.getEnemyField(),
|
||||
battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD,
|
||||
);
|
||||
const customModifierTypes = partyConfig?.pokemonConfigs
|
||||
?.filter(config => config?.modifierConfigs)
|
||||
.map(config => config.modifierConfigs!);
|
||||
|
@ -416,9 +466,8 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||
* @param moves
|
||||
*/
|
||||
export function loadCustomMovesForEncounter(moves: Moves | Moves[]) {
|
||||
moves = Array.isArray(moves) ? moves : [ moves ];
|
||||
return Promise.all(moves.map(move => initMoveAnim(move)))
|
||||
.then(() => loadMoveAnimAssets(moves));
|
||||
moves = Array.isArray(moves) ? moves : [moves];
|
||||
return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -427,7 +476,7 @@ export function loadCustomMovesForEncounter(moves: Moves | Moves[]) {
|
|||
* @param playSound
|
||||
* @param showMessage
|
||||
*/
|
||||
export function updatePlayerMoney(changeValue: number, playSound: boolean = true, showMessage: boolean = true) {
|
||||
export function updatePlayerMoney(changeValue: number, playSound = true, showMessage = true) {
|
||||
globalScene.money = Math.min(Math.max(globalScene.money + changeValue, 0), Number.MAX_SAFE_INTEGER);
|
||||
globalScene.updateMoneyText();
|
||||
globalScene.animateMoneyChanged(false);
|
||||
|
@ -436,9 +485,21 @@ export function updatePlayerMoney(changeValue: number, playSound: boolean = true
|
|||
}
|
||||
if (showMessage) {
|
||||
if (changeValue < 0) {
|
||||
globalScene.queueMessage(i18next.t("mysteryEncounterMessages:paid_money", { amount: -changeValue }), null, true);
|
||||
globalScene.queueMessage(
|
||||
i18next.t("mysteryEncounterMessages:paid_money", {
|
||||
amount: -changeValue,
|
||||
}),
|
||||
null,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
globalScene.queueMessage(i18next.t("mysteryEncounterMessages:receive_money", { amount: changeValue }), null, true);
|
||||
globalScene.queueMessage(
|
||||
i18next.t("mysteryEncounterMessages:receive_money", {
|
||||
amount: changeValue,
|
||||
}),
|
||||
null,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -461,7 +522,9 @@ export function generateModifierType(modifier: () => ModifierType, pregenArgs?:
|
|||
.withIdFromFunc(modifierTypes[modifierId])
|
||||
.withTierFromPool(ModifierPoolType.PLAYER, globalScene.getPlayerParty());
|
||||
|
||||
return result instanceof ModifierTypeGenerator ? result.generateType(globalScene.getPlayerParty(), pregenArgs) : result;
|
||||
return result instanceof ModifierTypeGenerator
|
||||
? result.generateType(globalScene.getPlayerParty(), pregenArgs)
|
||||
: result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -469,7 +532,10 @@ export function generateModifierType(modifier: () => ModifierType, pregenArgs?:
|
|||
* @param modifier
|
||||
* @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
|
||||
*/
|
||||
export function generateModifierTypeOption(modifier: () => ModifierType, pregenArgs?: any[]): ModifierTypeOption | null {
|
||||
export function generateModifierTypeOption(
|
||||
modifier: () => ModifierType,
|
||||
pregenArgs?: any[],
|
||||
): ModifierTypeOption | null {
|
||||
const result = generateModifierType(modifier, pregenArgs);
|
||||
if (result) {
|
||||
return new ModifierTypeOption(result, 0);
|
||||
|
@ -484,80 +550,100 @@ export function generateModifierTypeOption(modifier: () => ModifierType, pregenA
|
|||
* @param onPokemonNotSelected - Any logic that needs to be performed if no Pokemon is chosen
|
||||
* @param selectablePokemonFilter
|
||||
*/
|
||||
export function selectPokemonForOption(onPokemonSelected: (pokemon: PlayerPokemon) => void | OptionSelectItem[], onPokemonNotSelected?: () => void, selectablePokemonFilter?: PokemonSelectFilter): Promise<boolean> {
|
||||
export function selectPokemonForOption(
|
||||
// biome-ignore lint/suspicious/noConfusingVoidType: Takes a function that either returns void or an array of OptionSelectItem
|
||||
onPokemonSelected: (pokemon: PlayerPokemon) => void | OptionSelectItem[],
|
||||
onPokemonNotSelected?: () => void,
|
||||
selectablePokemonFilter?: PokemonSelectFilter,
|
||||
): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const modeToSetOnExit = globalScene.ui.getMode();
|
||||
|
||||
// Open party screen to choose pokemon
|
||||
globalScene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: number, option: PartyOption) => {
|
||||
if (slotIndex < globalScene.getPlayerParty().length) {
|
||||
globalScene.ui.setMode(modeToSetOnExit).then(() => {
|
||||
const pokemon = globalScene.getPlayerParty()[slotIndex];
|
||||
const secondaryOptions = onPokemonSelected(pokemon);
|
||||
if (!secondaryOptions) {
|
||||
globalScene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
globalScene.ui.setMode(
|
||||
Mode.PARTY,
|
||||
PartyUiMode.SELECT,
|
||||
-1,
|
||||
(slotIndex: number, _option: PartyOption) => {
|
||||
if (slotIndex < globalScene.getPlayerParty().length) {
|
||||
globalScene.ui.setMode(modeToSetOnExit).then(() => {
|
||||
const pokemon = globalScene.getPlayerParty()[slotIndex];
|
||||
const secondaryOptions = onPokemonSelected(pokemon);
|
||||
if (!secondaryOptions) {
|
||||
globalScene.currentBattle.mysteryEncounter!.setDialogueToken(
|
||||
"selectedPokemon",
|
||||
pokemon.getNameToRender(),
|
||||
);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// There is a second option to choose after selecting the Pokemon
|
||||
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
const displayOptions = () => {
|
||||
// Always appends a cancel option to bottom of options
|
||||
const fullOptions = secondaryOptions.map(option => {
|
||||
// Update handler to resolve promise
|
||||
const onSelect = option.handler;
|
||||
option.handler = () => {
|
||||
onSelect();
|
||||
globalScene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||
resolve(true);
|
||||
return true;
|
||||
// There is a second option to choose after selecting the Pokemon
|
||||
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
const displayOptions = () => {
|
||||
// Always appends a cancel option to bottom of options
|
||||
const fullOptions = secondaryOptions
|
||||
.map(option => {
|
||||
// Update handler to resolve promise
|
||||
const onSelect = option.handler;
|
||||
option.handler = () => {
|
||||
onSelect();
|
||||
globalScene.currentBattle.mysteryEncounter!.setDialogueToken(
|
||||
"selectedPokemon",
|
||||
pokemon.getNameToRender(),
|
||||
);
|
||||
resolve(true);
|
||||
return true;
|
||||
};
|
||||
return option;
|
||||
})
|
||||
.concat({
|
||||
label: i18next.t("menu:cancel"),
|
||||
handler: () => {
|
||||
globalScene.ui.clearText();
|
||||
globalScene.ui.setMode(modeToSetOnExit);
|
||||
resolve(false);
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false);
|
||||
},
|
||||
});
|
||||
|
||||
const config: OptionSelectConfig = {
|
||||
options: fullOptions,
|
||||
maxOptions: 7,
|
||||
yOffset: 0,
|
||||
supportHover: true,
|
||||
};
|
||||
return option;
|
||||
}).concat({
|
||||
label: i18next.t("menu:cancel"),
|
||||
handler: () => {
|
||||
globalScene.ui.clearText();
|
||||
globalScene.ui.setMode(modeToSetOnExit);
|
||||
resolve(false);
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false);
|
||||
}
|
||||
});
|
||||
|
||||
const config: OptionSelectConfig = {
|
||||
options: fullOptions,
|
||||
maxOptions: 7,
|
||||
yOffset: 0,
|
||||
supportHover: true
|
||||
// Do hover over the starting selection option
|
||||
if (fullOptions[0].onHover) {
|
||||
fullOptions[0].onHover();
|
||||
}
|
||||
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
|
||||
};
|
||||
|
||||
// Do hover over the starting selection option
|
||||
if (fullOptions[0].onHover) {
|
||||
fullOptions[0].onHover();
|
||||
const textPromptKey =
|
||||
globalScene.currentBattle.mysteryEncounter?.selectedOption?.dialogue?.secondOptionPrompt;
|
||||
if (!textPromptKey) {
|
||||
displayOptions();
|
||||
} else {
|
||||
showEncounterText(textPromptKey).then(() => displayOptions());
|
||||
}
|
||||
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
|
||||
};
|
||||
|
||||
const textPromptKey = globalScene.currentBattle.mysteryEncounter?.selectedOption?.dialogue?.secondOptionPrompt;
|
||||
if (!textPromptKey) {
|
||||
displayOptions();
|
||||
} else {
|
||||
showEncounterText(textPromptKey).then(() => displayOptions());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
globalScene.ui.setMode(modeToSetOnExit).then(() => {
|
||||
if (onPokemonNotSelected) {
|
||||
onPokemonNotSelected();
|
||||
}
|
||||
resolve(false);
|
||||
});
|
||||
}
|
||||
}, selectablePokemonFilter);
|
||||
} else {
|
||||
globalScene.ui.setMode(modeToSetOnExit).then(() => {
|
||||
if (onPokemonNotSelected) {
|
||||
onPokemonNotSelected();
|
||||
}
|
||||
resolve(false);
|
||||
});
|
||||
}
|
||||
},
|
||||
selectablePokemonFilter,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -575,7 +661,12 @@ interface PokemonAndOptionSelected {
|
|||
* @param selectablePokemonFilter
|
||||
* @param onHoverOverCancelOption
|
||||
*/
|
||||
export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelectPromptKey: string, selectablePokemonFilter?: PokemonSelectFilter, onHoverOverCancelOption?: () => void): Promise<PokemonAndOptionSelected | null> {
|
||||
export function selectOptionThenPokemon(
|
||||
options: OptionSelectItem[],
|
||||
optionSelectPromptKey: string,
|
||||
selectablePokemonFilter?: PokemonSelectFilter,
|
||||
onHoverOverCancelOption?: () => void,
|
||||
): Promise<PokemonAndOptionSelected | null> {
|
||||
return new Promise<PokemonAndOptionSelected | null>(resolve => {
|
||||
const modeToSetOnExit = globalScene.ui.getMode();
|
||||
|
||||
|
@ -601,51 +692,62 @@ export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelec
|
|||
|
||||
const selectPokemonAfterOption = (selectedOptionIndex: number) => {
|
||||
// Open party screen to choose a Pokemon
|
||||
globalScene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: number, option: PartyOption) => {
|
||||
if (slotIndex < globalScene.getPlayerParty().length) {
|
||||
// Pokemon and option selected
|
||||
globalScene.ui.setMode(modeToSetOnExit).then(() => {
|
||||
const result: PokemonAndOptionSelected = { selectedPokemonIndex: slotIndex, selectedOptionIndex: selectedOptionIndex };
|
||||
resolve(result);
|
||||
});
|
||||
} else {
|
||||
// Back to first option select screen
|
||||
displayOptions(config);
|
||||
}
|
||||
}, selectablePokemonFilter);
|
||||
globalScene.ui.setMode(
|
||||
Mode.PARTY,
|
||||
PartyUiMode.SELECT,
|
||||
-1,
|
||||
(slotIndex: number, _option: PartyOption) => {
|
||||
if (slotIndex < globalScene.getPlayerParty().length) {
|
||||
// Pokemon and option selected
|
||||
globalScene.ui.setMode(modeToSetOnExit).then(() => {
|
||||
const result: PokemonAndOptionSelected = {
|
||||
selectedPokemonIndex: slotIndex,
|
||||
selectedOptionIndex: selectedOptionIndex,
|
||||
};
|
||||
resolve(result);
|
||||
});
|
||||
} else {
|
||||
// Back to first option select screen
|
||||
displayOptions(config);
|
||||
}
|
||||
},
|
||||
selectablePokemonFilter,
|
||||
);
|
||||
};
|
||||
|
||||
// Always appends a cancel option to bottom of options
|
||||
const fullOptions = options.map((option, index) => {
|
||||
// Update handler to resolve promise
|
||||
const onSelect = option.handler;
|
||||
option.handler = () => {
|
||||
onSelect();
|
||||
selectPokemonAfterOption(index);
|
||||
return true;
|
||||
};
|
||||
return option;
|
||||
}).concat({
|
||||
label: i18next.t("menu:cancel"),
|
||||
handler: () => {
|
||||
globalScene.ui.clearText();
|
||||
globalScene.ui.setMode(modeToSetOnExit);
|
||||
resolve(null);
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
if (onHoverOverCancelOption) {
|
||||
onHoverOverCancelOption();
|
||||
}
|
||||
showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false);
|
||||
}
|
||||
});
|
||||
const fullOptions = options
|
||||
.map((option, index) => {
|
||||
// Update handler to resolve promise
|
||||
const onSelect = option.handler;
|
||||
option.handler = () => {
|
||||
onSelect();
|
||||
selectPokemonAfterOption(index);
|
||||
return true;
|
||||
};
|
||||
return option;
|
||||
})
|
||||
.concat({
|
||||
label: i18next.t("menu:cancel"),
|
||||
handler: () => {
|
||||
globalScene.ui.clearText();
|
||||
globalScene.ui.setMode(modeToSetOnExit);
|
||||
resolve(null);
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
if (onHoverOverCancelOption) {
|
||||
onHoverOverCancelOption();
|
||||
}
|
||||
showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false);
|
||||
},
|
||||
});
|
||||
|
||||
const config: OptionSelectConfig = {
|
||||
options: fullOptions,
|
||||
maxOptions: 7,
|
||||
yOffset: 0,
|
||||
supportHover: true
|
||||
supportHover: true,
|
||||
};
|
||||
|
||||
displayOptions(config);
|
||||
|
@ -659,7 +761,11 @@ export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelec
|
|||
* @param eggRewards
|
||||
* @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before {@linkcode MysteryEncounterRewardsPhase})
|
||||
*/
|
||||
export function setEncounterRewards(customShopRewards?: CustomModifierSettings, eggRewards?: IEggOptions[], preRewardsCallback?: Function) {
|
||||
export function setEncounterRewards(
|
||||
customShopRewards?: CustomModifierSettings,
|
||||
eggRewards?: IEggOptions[],
|
||||
preRewardsCallback?: Function,
|
||||
) {
|
||||
globalScene.currentBattle.mysteryEncounter!.doEncounterRewards = () => {
|
||||
if (preRewardsCallback) {
|
||||
preRewardsCallback();
|
||||
|
@ -702,8 +808,8 @@ export function setEncounterRewards(customShopRewards?: CustomModifierSettings,
|
|||
* https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_effort_value_yield_(Generation_IX)
|
||||
* @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue
|
||||
*/
|
||||
export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex: boolean = true) {
|
||||
const participantIds = Array.isArray(participantId) ? participantId : [ participantId ];
|
||||
export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex = true) {
|
||||
const participantIds = Array.isArray(participantId) ? participantId : [participantId];
|
||||
|
||||
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => {
|
||||
globalScene.unshiftPhase(new PartyExpPhase(baseExpValue, useWaveIndex, new Set(participantIds)));
|
||||
|
@ -737,7 +843,10 @@ export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSet
|
|||
* @param addHealPhase - when true, will add a shop phase to end of encounter with 0 rewards but healing items are available
|
||||
* @param encounterMode - Can set custom encounter mode if necessary (may be required for forcing Pokemon to return before next phase)
|
||||
*/
|
||||
export function leaveEncounterWithoutBattle(addHealPhase: boolean = false, encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE) {
|
||||
export function leaveEncounterWithoutBattle(
|
||||
addHealPhase = false,
|
||||
encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE,
|
||||
) {
|
||||
globalScene.currentBattle.mysteryEncounter!.encounterMode = encounterMode;
|
||||
globalScene.clearPhaseQueue();
|
||||
globalScene.clearPhaseQueueSplice();
|
||||
|
@ -749,8 +858,8 @@ export function leaveEncounterWithoutBattle(addHealPhase: boolean = false, encou
|
|||
* @param addHealPhase - Adds an empty shop phase to allow player to purchase healing items
|
||||
* @param doNotContinue - default `false`. If set to true, will not end the battle and continue to next wave
|
||||
*/
|
||||
export function handleMysteryEncounterVictory(addHealPhase: boolean = false, doNotContinue: boolean = false) {
|
||||
const allowedPkm = globalScene.getPlayerParty().filter((pkm) => pkm.isAllowedInBattle());
|
||||
export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinue = false) {
|
||||
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
|
||||
|
||||
if (allowedPkm.length === 0) {
|
||||
globalScene.clearPhaseQueue();
|
||||
|
@ -763,10 +872,17 @@ export function handleMysteryEncounterVictory(addHealPhase: boolean = false, doN
|
|||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.continuousEncounter || doNotContinue) {
|
||||
return;
|
||||
} else if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
|
||||
}
|
||||
if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
|
||||
globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase));
|
||||
globalScene.pushPhase(new EggLapsePhase());
|
||||
} else if (!globalScene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
|
||||
} else if (
|
||||
!globalScene
|
||||
.getEnemyParty()
|
||||
.find(p =>
|
||||
encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true),
|
||||
)
|
||||
) {
|
||||
globalScene.pushPhase(new BattleEndPhase(true));
|
||||
if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
|
||||
globalScene.pushPhase(new TrainerVictoryPhase());
|
||||
|
@ -785,8 +901,8 @@ export function handleMysteryEncounterVictory(addHealPhase: boolean = false, doN
|
|||
* Similar to {@linkcode handleMysteryEncounterVictory}, but for cases where the player lost a battle or failed a challenge
|
||||
* @param addHealPhase
|
||||
*/
|
||||
export function handleMysteryEncounterBattleFailed(addHealPhase: boolean = false, doNotContinue: boolean = false) {
|
||||
const allowedPkm = globalScene.getPlayerParty().filter((pkm) => pkm.isAllowedInBattle());
|
||||
export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotContinue = false) {
|
||||
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
|
||||
|
||||
if (allowedPkm.length === 0) {
|
||||
globalScene.clearPhaseQueue();
|
||||
|
@ -799,7 +915,8 @@ export function handleMysteryEncounterBattleFailed(addHealPhase: boolean = false
|
|||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.continuousEncounter || doNotContinue) {
|
||||
return;
|
||||
} else if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
|
||||
}
|
||||
if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
|
||||
globalScene.pushPhase(new BattleEndPhase(false));
|
||||
}
|
||||
|
||||
|
@ -817,7 +934,7 @@ export function handleMysteryEncounterBattleFailed(addHealPhase: boolean = false
|
|||
* @param destroy - If true, will destroy visuals ONLY ON HIDE TRANSITION. Does nothing on show. Defaults to true
|
||||
* @param duration
|
||||
*/
|
||||
export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, destroy: boolean = true, duration: number = 750): Promise<boolean> {
|
||||
export function transitionMysteryEncounterIntroVisuals(hide = true, destroy = true, duration = 750): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const introVisuals = globalScene.currentBattle.mysteryEncounter!.introVisuals;
|
||||
const enemyPokemon = globalScene.getEnemyField();
|
||||
|
@ -835,7 +952,7 @@ export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, des
|
|||
|
||||
// Transition
|
||||
globalScene.tweens.add({
|
||||
targets: [ introVisuals, enemyPokemon ],
|
||||
targets: [introVisuals, enemyPokemon],
|
||||
x: `${hide ? "+" : "-"}=16`,
|
||||
y: `${hide ? "-" : "+"}=16`,
|
||||
alpha: hide ? 0 : 1,
|
||||
|
@ -852,7 +969,7 @@ export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, des
|
|||
globalScene.currentBattle.mysteryEncounter!.introVisuals = undefined;
|
||||
}
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
|
@ -866,10 +983,15 @@ export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, des
|
|||
*/
|
||||
export function handleMysteryEncounterBattleStartEffects() {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter;
|
||||
if (globalScene.currentBattle.isBattleMysteryEncounter() && encounter && encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE && !encounter.startOfBattleEffectsComplete) {
|
||||
if (
|
||||
globalScene.currentBattle.isBattleMysteryEncounter() &&
|
||||
encounter &&
|
||||
encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE &&
|
||||
!encounter.startOfBattleEffectsComplete
|
||||
) {
|
||||
const effects = encounter.startOfBattleEffects;
|
||||
effects.forEach(effect => {
|
||||
let source;
|
||||
let source: EnemyPokemon | Pokemon;
|
||||
if (effect.sourcePokemon) {
|
||||
source = effect.sourcePokemon;
|
||||
} else if (!isNullOrUndefined(effect.sourceBattlerIndex)) {
|
||||
|
@ -887,6 +1009,7 @@ export function handleMysteryEncounterBattleStartEffects() {
|
|||
} else {
|
||||
source = globalScene.getEnemyField()[0];
|
||||
}
|
||||
// @ts-ignore: source cannot be undefined
|
||||
globalScene.pushPhase(new MovePhase(source, effect.targets, effect.move, effect.followUp, effect.ignorePp));
|
||||
});
|
||||
|
||||
|
@ -919,20 +1042,31 @@ export function handleMysteryEncounterTurnStartEffects(): boolean {
|
|||
* @param rerollHidden whether the mon should get an extra roll for Hidden Ability
|
||||
* @returns {@linkcode EnemyPokemon} for the requested encounter
|
||||
*/
|
||||
export function getRandomEncounterSpecies(level: number, isBoss: boolean = false, rerollHidden: boolean = false): EnemyPokemon {
|
||||
export function getRandomEncounterSpecies(level: number, isBoss = false, rerollHidden = false): EnemyPokemon {
|
||||
let bossSpecies: PokemonSpecies;
|
||||
let isEventEncounter = false;
|
||||
const eventEncounters = globalScene.eventManager.getEventEncounters();
|
||||
let formIndex;
|
||||
let formIndex: number | undefined;
|
||||
|
||||
if (eventEncounters.length > 0 && randSeedInt(2) === 1) {
|
||||
const eventEncounter = randSeedItem(eventEncounters);
|
||||
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, !eventEncounter.blockEvolution, isBoss, globalScene.gameMode);
|
||||
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(
|
||||
level,
|
||||
!eventEncounter.blockEvolution,
|
||||
isBoss,
|
||||
globalScene.gameMode,
|
||||
);
|
||||
isEventEncounter = true;
|
||||
bossSpecies = getPokemonSpecies(levelSpecies);
|
||||
formIndex = eventEncounter.formIndex;
|
||||
} else {
|
||||
bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), isBoss);
|
||||
bossSpecies = globalScene.arena.randomSpecies(
|
||||
globalScene.currentBattle.waveIndex,
|
||||
level,
|
||||
0,
|
||||
getPartyLuckValue(globalScene.getPlayerParty()),
|
||||
isBoss,
|
||||
);
|
||||
}
|
||||
const ret = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, isBoss);
|
||||
if (formIndex) {
|
||||
|
@ -959,15 +1093,24 @@ export function getRandomEncounterSpecies(level: number, isBoss: boolean = false
|
|||
export function calculateMEAggregateStats(baseSpawnWeight: number) {
|
||||
const numRuns = 1000;
|
||||
let run = 0;
|
||||
const biomes = Object.keys(Biome).filter(key => isNaN(Number(key)));
|
||||
const alwaysPickTheseBiomes = [ Biome.ISLAND, Biome.ABYSS, Biome.WASTELAND, Biome.FAIRY_CAVE, Biome.TEMPLE, Biome.LABORATORY, Biome.SPACE, Biome.WASTELAND ];
|
||||
const biomes = Object.keys(Biome).filter(key => Number.isNaN(Number(key)));
|
||||
const alwaysPickTheseBiomes = [
|
||||
Biome.ISLAND,
|
||||
Biome.ABYSS,
|
||||
Biome.WASTELAND,
|
||||
Biome.FAIRY_CAVE,
|
||||
Biome.TEMPLE,
|
||||
Biome.LABORATORY,
|
||||
Biome.SPACE,
|
||||
Biome.WASTELAND,
|
||||
];
|
||||
|
||||
const calculateNumEncounters = (): any[] => {
|
||||
let encounterRate = baseSpawnWeight; // BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
|
||||
const numEncounters = [ 0, 0, 0, 0 ];
|
||||
const numEncounters = [0, 0, 0, 0];
|
||||
let mostRecentEncounterWave = 0;
|
||||
const encountersByBiome = new Map<string, number>(biomes.map(b => [ b, 0 ]));
|
||||
const validMEfloorsByBiome = new Map<string, number>(biomes.map(b => [ b, 0 ]));
|
||||
const encountersByBiome = new Map<string, number>(biomes.map(b => [b, 0]));
|
||||
const validMEfloorsByBiome = new Map<string, number>(biomes.map(b => [b, 0]));
|
||||
let currentBiome = Biome.TOWN;
|
||||
let currentArena = globalScene.newArena(currentBiome);
|
||||
globalScene.setSeed(Utils.randomString(24));
|
||||
|
@ -987,7 +1130,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
|
|||
.filter(b => {
|
||||
return !Array.isArray(b) || !Utils.randSeedInt(b[1]);
|
||||
})
|
||||
.map(b => !Array.isArray(b) ? b : b[0]);
|
||||
.map(b => (!Array.isArray(b) ? b : b[0]));
|
||||
}, i * 100);
|
||||
if (biomes! && biomes.length > 0) {
|
||||
const specialBiomes = biomes.filter(b => alwaysPickTheseBiomes.includes(b));
|
||||
|
@ -998,7 +1141,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
|
|||
}
|
||||
}
|
||||
} else if (biomeLinks.hasOwnProperty(currentBiome)) {
|
||||
currentBiome = (biomeLinks[currentBiome] as Biome);
|
||||
currentBiome = biomeLinks[currentBiome] as Biome;
|
||||
} else {
|
||||
if (!(i % 50)) {
|
||||
currentBiome = Biome.END;
|
||||
|
@ -1027,12 +1170,12 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
|
|||
|
||||
// If total number of encounters is lower than expected for the run, slightly favor a new encounter
|
||||
// Do the reverse as well
|
||||
const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (180 - 10) * (i - 10);
|
||||
const expectedEncountersByFloor = (AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (180 - 10)) * (i - 10);
|
||||
const currentRunDiffFromAvg = expectedEncountersByFloor - numEncounters.reduce((a, b) => a + b);
|
||||
const favoredEncounterRate = encounterRate + currentRunDiffFromAvg * 15;
|
||||
|
||||
// If the most recent ME was 3 or fewer waves ago, can never spawn a ME
|
||||
const canSpawn = (i - mostRecentEncounterWave) > 3;
|
||||
const canSpawn = i - mostRecentEncounterWave > 3;
|
||||
|
||||
if (canSpawn && roll < favoredEncounterRate) {
|
||||
mostRecentEncounterWave = i;
|
||||
|
@ -1040,7 +1183,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
|
|||
|
||||
// Calculate encounter rarity
|
||||
// Common / Uncommon / Rare / Super Rare (base is out of 128)
|
||||
const tierWeights = [ 66, 40, 19, 3 ];
|
||||
const tierWeights = [66, 40, 19, 3];
|
||||
|
||||
// Adjust tier weights by currently encountered events (pity system that lowers odds of multiple Common/Great)
|
||||
tierWeights[0] = tierWeights[0] - 6 * numEncounters[0];
|
||||
|
@ -1052,14 +1195,20 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
|
|||
const uncommonThreshold = totalWeight - tierWeights[0] - tierWeights[1]; // 64 - 32 - 16 = 16
|
||||
const rareThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2]; // 64 - 32 - 16 - 10 = 6
|
||||
|
||||
tierValue > commonThreshold ? ++numEncounters[0] : tierValue > uncommonThreshold ? ++numEncounters[1] : tierValue > rareThreshold ? ++numEncounters[2] : ++numEncounters[3];
|
||||
tierValue > commonThreshold
|
||||
? ++numEncounters[0]
|
||||
: tierValue > uncommonThreshold
|
||||
? ++numEncounters[1]
|
||||
: tierValue > rareThreshold
|
||||
? ++numEncounters[2]
|
||||
: ++numEncounters[3];
|
||||
encountersByBiome.set(Biome[currentBiome], (encountersByBiome.get(Biome[currentBiome]) ?? 0) + 1);
|
||||
} else {
|
||||
encounterRate += WEIGHT_INCREMENT_ON_SPAWN_MISS;
|
||||
}
|
||||
}
|
||||
|
||||
return [ numEncounters, encountersByBiome, validMEfloorsByBiome ];
|
||||
return [numEncounters, encountersByBiome, validMEfloorsByBiome];
|
||||
};
|
||||
|
||||
const encounterRuns: number[][] = [];
|
||||
|
@ -1067,7 +1216,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
|
|||
const validFloorsByBiome: Map<string, number>[] = [];
|
||||
while (run < numRuns) {
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
const [ numEncounters, encountersByBiome, validMEfloorsByBiome ] = calculateNumEncounters();
|
||||
const [numEncounters, encountersByBiome, validMEfloorsByBiome] = calculateNumEncounters();
|
||||
encounterRuns.push(numEncounters);
|
||||
encountersByBiomeRuns.push(encountersByBiome);
|
||||
validFloorsByBiome.push(validMEfloorsByBiome);
|
||||
|
@ -1108,13 +1257,17 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
|
|||
|
||||
let stats = `Starting weight: ${baseSpawnWeight}\nAverage MEs per run: ${totalMean}\nStandard Deviation: ${totalStd}\nAvg Commons: ${commonMean}\nAvg Greats: ${uncommonMean}\nAvg Ultras: ${rareMean}\nAvg Rogues: ${superRareMean}\n`;
|
||||
|
||||
const meanEncountersPerRunPerBiomeSorted = [ ...meanEncountersPerRunPerBiome.entries() ].sort((e1, e2) => e2[1] - e1[1]);
|
||||
meanEncountersPerRunPerBiomeSorted.forEach(value => stats = stats + `${value[0]}: avg valid floors ${meanMEFloorsPerRunPerBiome.get(value[0])}, avg MEs ${value[1]},\n`);
|
||||
const meanEncountersPerRunPerBiomeSorted = [...meanEncountersPerRunPerBiome.entries()].sort(
|
||||
(e1, e2) => e2[1] - e1[1],
|
||||
);
|
||||
|
||||
for (const value of meanEncountersPerRunPerBiomeSorted) {
|
||||
stats += value[0] + "avg valid floors " + meanMEFloorsPerRunPerBiome.get(value[0]) + ", avg MEs ${value[1]},\n";
|
||||
}
|
||||
|
||||
console.log(stats);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO: remove once encounter spawn rate is finalized
|
||||
* Just a helper function to calculate aggregate stats for MEs in a Classic run
|
||||
|
@ -1125,7 +1278,7 @@ export function calculateRareSpawnAggregateStats(luckValue: number) {
|
|||
let run = 0;
|
||||
|
||||
const calculateNumRareEncounters = (): any[] => {
|
||||
const bossEncountersByRarity = [ 0, 0, 0, 0 ];
|
||||
const bossEncountersByRarity = [0, 0, 0, 0];
|
||||
globalScene.setSeed(Utils.randomString(24));
|
||||
globalScene.resetSeed();
|
||||
// There are 12 wild boss floors
|
||||
|
@ -1133,17 +1286,20 @@ export function calculateRareSpawnAggregateStats(luckValue: number) {
|
|||
// Roll boss tier
|
||||
// luck influences encounter rarity
|
||||
let luckModifier = 0;
|
||||
if (!isNaN(luckValue)) {
|
||||
if (!Number.isNaN(luckValue)) {
|
||||
luckModifier = luckValue * 0.5;
|
||||
}
|
||||
const tierValue = Utils.randSeedInt(64 - luckModifier);
|
||||
const tier = tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE;
|
||||
const tier =
|
||||
tierValue >= 20
|
||||
? BiomePoolTier.BOSS
|
||||
: tierValue >= 6
|
||||
? BiomePoolTier.BOSS_RARE
|
||||
: tierValue >= 1
|
||||
? BiomePoolTier.BOSS_SUPER_RARE
|
||||
: BiomePoolTier.BOSS_ULTRA_RARE;
|
||||
|
||||
switch (tier) {
|
||||
default:
|
||||
case BiomePoolTier.BOSS:
|
||||
++bossEncountersByRarity[0];
|
||||
break;
|
||||
case BiomePoolTier.BOSS_RARE:
|
||||
++bossEncountersByRarity[1];
|
||||
break;
|
||||
|
@ -1153,6 +1309,10 @@ export function calculateRareSpawnAggregateStats(luckValue: number) {
|
|||
case BiomePoolTier.BOSS_ULTRA_RARE:
|
||||
++bossEncountersByRarity[3];
|
||||
break;
|
||||
case BiomePoolTier.BOSS:
|
||||
default:
|
||||
++bossEncountersByRarity[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,12 @@ import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
|||
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor } from "#app/data/pokeball";
|
||||
import {
|
||||
doPokeballBounceAnim,
|
||||
getPokeballAtlasKey,
|
||||
getPokeballCatchMultiplier,
|
||||
getPokeballTintColor,
|
||||
} from "#app/data/pokeball";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims";
|
||||
import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect";
|
||||
|
@ -17,7 +22,11 @@ import type { PokemonType } from "#enums/pokemon-type";
|
|||
import type PokemonSpecies from "#app/data/pokemon-species";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { speciesStarterCosts } from "#app/data/balance/starters";
|
||||
import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import {
|
||||
getEncounterText,
|
||||
queueEncounterMessage,
|
||||
showEncounterText,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
|
@ -41,18 +50,41 @@ export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1;
|
|||
* @param shiny
|
||||
* @param variant
|
||||
*/
|
||||
export function getSpriteKeysFromSpecies(species: Species, female?: boolean, formIndex?: number, shiny?: boolean, variant?: number): { spriteKey: string, fileRoot: string } {
|
||||
const spriteKey = getPokemonSpecies(species).getSpriteKey(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
|
||||
const fileRoot = getPokemonSpecies(species).getSpriteAtlasPath(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
|
||||
export function getSpriteKeysFromSpecies(
|
||||
species: Species,
|
||||
female?: boolean,
|
||||
formIndex?: number,
|
||||
shiny?: boolean,
|
||||
variant?: number,
|
||||
): { spriteKey: string; fileRoot: string } {
|
||||
const spriteKey = getPokemonSpecies(species).getSpriteKey(
|
||||
female ?? false,
|
||||
formIndex ?? 0,
|
||||
shiny ?? false,
|
||||
variant ?? 0,
|
||||
);
|
||||
const fileRoot = getPokemonSpecies(species).getSpriteAtlasPath(
|
||||
female ?? false,
|
||||
formIndex ?? 0,
|
||||
shiny ?? false,
|
||||
variant ?? 0,
|
||||
);
|
||||
return { spriteKey, fileRoot };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sprite key and file root for a given Pokemon (accounts for gender, shiny, variants, forms, and experimental)
|
||||
*/
|
||||
export function getSpriteKeysFromPokemon(pokemon: Pokemon): { spriteKey: string, fileRoot: string } {
|
||||
const spriteKey = pokemon.getSpeciesForm().getSpriteKey(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant);
|
||||
const fileRoot = pokemon.getSpeciesForm().getSpriteAtlasPath(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant);
|
||||
export function getSpriteKeysFromPokemon(pokemon: Pokemon): {
|
||||
spriteKey: string;
|
||||
fileRoot: string;
|
||||
} {
|
||||
const spriteKey = pokemon
|
||||
.getSpeciesForm()
|
||||
.getSpriteKey(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant);
|
||||
const fileRoot = pokemon
|
||||
.getSpeciesForm()
|
||||
.getSpriteAtlasPath(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant);
|
||||
|
||||
return { spriteKey, fileRoot };
|
||||
}
|
||||
|
@ -65,7 +97,11 @@ export function getSpriteKeysFromPokemon(pokemon: Pokemon): { spriteKey: string,
|
|||
* @param doNotReturnLastAllowedMon Default `false`. If `true`, will never return the last unfainted pokemon in the party. Useful when this function is being used to determine what Pokemon to remove from the party (Don't want to remove last unfainted)
|
||||
* @returns
|
||||
*/
|
||||
export function getRandomPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false, doNotReturnLastAllowedMon: boolean = false): PlayerPokemon {
|
||||
export function getRandomPlayerPokemon(
|
||||
isAllowed = false,
|
||||
isFainted = false,
|
||||
doNotReturnLastAllowedMon = false,
|
||||
): PlayerPokemon {
|
||||
const party = globalScene.getPlayerParty();
|
||||
let chosenIndex: number;
|
||||
let chosenPokemon: PlayerPokemon | null = null;
|
||||
|
@ -104,7 +140,7 @@ export function getRandomPlayerPokemon(isAllowed: boolean = false, isFainted: bo
|
|||
* @param isFainted Default false. If true, includes fainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getHighestLevelPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
|
||||
export function getHighestLevelPlayerPokemon(isAllowed = false, isFainted = false): PlayerPokemon {
|
||||
const party = globalScene.getPlayerParty();
|
||||
let pokemon: PlayerPokemon | null = null;
|
||||
|
||||
|
@ -116,7 +152,7 @@ export function getHighestLevelPlayerPokemon(isAllowed: boolean = false, isFaint
|
|||
continue;
|
||||
}
|
||||
|
||||
pokemon = pokemon ? pokemon?.level < p?.level ? p : pokemon : p;
|
||||
pokemon = pokemon ? (pokemon?.level < p?.level ? p : pokemon) : p;
|
||||
}
|
||||
|
||||
return pokemon!;
|
||||
|
@ -130,7 +166,7 @@ export function getHighestLevelPlayerPokemon(isAllowed: boolean = false, isFaint
|
|||
* @param isFainted Default false. If true, includes fainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getHighestStatPlayerPokemon(stat: PermanentStat, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
|
||||
export function getHighestStatPlayerPokemon(stat: PermanentStat, isAllowed = false, isFainted = false): PlayerPokemon {
|
||||
const party = globalScene.getPlayerParty();
|
||||
let pokemon: PlayerPokemon | null = null;
|
||||
|
||||
|
@ -142,7 +178,7 @@ export function getHighestStatPlayerPokemon(stat: PermanentStat, isAllowed: bool
|
|||
continue;
|
||||
}
|
||||
|
||||
pokemon = pokemon ? pokemon.getStat(stat) < p?.getStat(stat) ? p : pokemon : p;
|
||||
pokemon = pokemon ? (pokemon.getStat(stat) < p?.getStat(stat) ? p : pokemon) : p;
|
||||
}
|
||||
|
||||
return pokemon!;
|
||||
|
@ -155,7 +191,7 @@ export function getHighestStatPlayerPokemon(stat: PermanentStat, isAllowed: bool
|
|||
* @param isFainted Default false. If true, includes fainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getLowestLevelPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
|
||||
export function getLowestLevelPlayerPokemon(isAllowed = false, isFainted = false): PlayerPokemon {
|
||||
const party = globalScene.getPlayerParty();
|
||||
let pokemon: PlayerPokemon | null = null;
|
||||
|
||||
|
@ -167,7 +203,7 @@ export function getLowestLevelPlayerPokemon(isAllowed: boolean = false, isFainte
|
|||
continue;
|
||||
}
|
||||
|
||||
pokemon = pokemon ? pokemon?.level > p?.level ? p : pokemon : p;
|
||||
pokemon = pokemon ? (pokemon?.level > p?.level ? p : pokemon) : p;
|
||||
}
|
||||
|
||||
return pokemon!;
|
||||
|
@ -180,7 +216,7 @@ export function getLowestLevelPlayerPokemon(isAllowed: boolean = false, isFainte
|
|||
* @param isFainted Default false. If true, includes fainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getHighestStatTotalPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
|
||||
export function getHighestStatTotalPlayerPokemon(isAllowed = false, isFainted = false): PlayerPokemon {
|
||||
const party = globalScene.getPlayerParty();
|
||||
let pokemon: PlayerPokemon | null = null;
|
||||
|
||||
|
@ -192,7 +228,7 @@ export function getHighestStatTotalPlayerPokemon(isAllowed: boolean = false, isF
|
|||
continue;
|
||||
}
|
||||
|
||||
pokemon = pokemon ? pokemon?.stats.reduce((a, b) => a + b) < p?.stats.reduce((a, b) => a + b) ? p : pokemon : p;
|
||||
pokemon = pokemon ? (pokemon?.stats.reduce((a, b) => a + b) < p?.stats.reduce((a, b) => a + b) ? p : pokemon) : p;
|
||||
}
|
||||
|
||||
return pokemon!;
|
||||
|
@ -209,28 +245,40 @@ export function getHighestStatTotalPlayerPokemon(isAllowed: boolean = false, isF
|
|||
* @param allowMythical
|
||||
* @returns
|
||||
*/
|
||||
export function getRandomSpeciesByStarterCost(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: 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 max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers;
|
||||
|
||||
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarterCosts)
|
||||
.map(s => [ parseInt(s) as Species, speciesStarterCosts[s] as number ])
|
||||
.map(s => [Number.parseInt(s) as Species, speciesStarterCosts[s] as number])
|
||||
.filter(s => {
|
||||
const pokemonSpecies = getPokemonSpecies(s[0]);
|
||||
return pokemonSpecies && (!excludedSpecies || !excludedSpecies.includes(s[0]))
|
||||
&& (allowSubLegendary || !pokemonSpecies.subLegendary)
|
||||
&& (allowLegendary || !pokemonSpecies.legendary)
|
||||
&& (allowMythical || !pokemonSpecies.mythical);
|
||||
return (
|
||||
pokemonSpecies &&
|
||||
(!excludedSpecies || !excludedSpecies.includes(s[0])) &&
|
||||
(allowSubLegendary || !pokemonSpecies.subLegendary) &&
|
||||
(allowLegendary || !pokemonSpecies.legendary) &&
|
||||
(allowMythical || !pokemonSpecies.mythical)
|
||||
);
|
||||
})
|
||||
.map(s => [ getPokemonSpecies(s[0]), s[1] ]);
|
||||
.map(s => [getPokemonSpecies(s[0]), s[1]]);
|
||||
|
||||
if (types && types.length > 0) {
|
||||
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2)));
|
||||
filteredSpecies = filteredSpecies.filter(
|
||||
s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2)),
|
||||
);
|
||||
}
|
||||
|
||||
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are
|
||||
// Starts by decrementing starter tier min until it is 0, then increments tier max up to 10
|
||||
let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => (s[1] >= min && s[1] <= max));
|
||||
let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => s[1] >= min && s[1] <= max);
|
||||
while (tryFilterStarterTiers.length === 0 && !(min === 0 && max === 10)) {
|
||||
if (min > 0) {
|
||||
min--;
|
||||
|
@ -259,7 +307,11 @@ export function koPlayerPokemon(pokemon: PlayerPokemon) {
|
|||
pokemon.hp = 0;
|
||||
pokemon.trySetStatus(StatusEffect.FAINT);
|
||||
pokemon.updateInfo();
|
||||
queueEncounterMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||
queueEncounterMessage(
|
||||
i18next.t("battle:fainted", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -290,7 +342,9 @@ function applyHpChangeToPokemon(pokemon: PlayerPokemon, value: number) {
|
|||
*/
|
||||
export function applyDamageToPokemon(pokemon: PlayerPokemon, damage: number) {
|
||||
if (damage <= 0) {
|
||||
console.warn("Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead.");
|
||||
console.warn(
|
||||
"Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead.",
|
||||
);
|
||||
}
|
||||
// If a Pokemon would faint from the damage applied, its HP is instead set to 1.
|
||||
if (pokemon.isAllowedInBattle() && pokemon.hp - damage <= 0) {
|
||||
|
@ -308,7 +362,9 @@ export function applyDamageToPokemon(pokemon: PlayerPokemon, damage: number) {
|
|||
*/
|
||||
export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) {
|
||||
if (heal <= 0) {
|
||||
console.warn("Damaging pokemon with `applyHealToPokemon` is not recommended! Please use `applyDamageToPokemon` instead.");
|
||||
console.warn(
|
||||
"Damaging pokemon with `applyHealToPokemon` is not recommended! Please use `applyDamageToPokemon` instead.",
|
||||
);
|
||||
}
|
||||
|
||||
applyHpChangeToPokemon(pokemon, heal);
|
||||
|
@ -321,8 +377,9 @@ export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) {
|
|||
* @param value
|
||||
*/
|
||||
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
|
||||
const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE()
|
||||
.generateType(globalScene.getPlayerParty(), [ value ])
|
||||
const modType = modifierTypes
|
||||
.MYSTERY_ENCOUNTER_SHUCKLE_JUICE()
|
||||
.generateType(globalScene.getPlayerParty(), [value])
|
||||
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE);
|
||||
const modifier = modType?.newModifier(pokemon);
|
||||
if (modifier) {
|
||||
|
@ -339,15 +396,20 @@ export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: numb
|
|||
* @param modType
|
||||
* @param fallbackModifierType
|
||||
*/
|
||||
export async function applyModifierTypeToPlayerPokemon(pokemon: PlayerPokemon, modType: PokemonHeldItemModifierType, fallbackModifierType?: PokemonHeldItemModifierType) {
|
||||
export async function applyModifierTypeToPlayerPokemon(
|
||||
pokemon: PlayerPokemon,
|
||||
modType: PokemonHeldItemModifierType,
|
||||
fallbackModifierType?: PokemonHeldItemModifierType,
|
||||
) {
|
||||
// Check if the Pokemon has max stacks of that item already
|
||||
const modifier = modType.newModifier(pokemon);
|
||||
const existing = globalScene.findModifier(m => (
|
||||
m instanceof PokemonHeldItemModifier &&
|
||||
m.type.id === modType.id &&
|
||||
m.pokemonId === pokemon.id &&
|
||||
m.matchType(modifier)
|
||||
)) as PokemonHeldItemModifier;
|
||||
const existing = globalScene.findModifier(
|
||||
m =>
|
||||
m instanceof PokemonHeldItemModifier &&
|
||||
m.type.id === modType.id &&
|
||||
m.pokemonId === pokemon.id &&
|
||||
m.matchType(modifier),
|
||||
) as PokemonHeldItemModifier;
|
||||
|
||||
// At max stacks
|
||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
||||
|
@ -373,7 +435,11 @@ export async function applyModifierTypeToPlayerPokemon(pokemon: PlayerPokemon, m
|
|||
* @param pokeballType
|
||||
* @param ballTwitchRate - can pass custom ball catch rates (for special events, like safari)
|
||||
*/
|
||||
export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: PokeballType, ballTwitchRate?: number): Promise<boolean> {
|
||||
export function trainerThrowPokeball(
|
||||
pokemon: EnemyPokemon,
|
||||
pokeballType: PokeballType,
|
||||
ballTwitchRate?: number,
|
||||
): Promise<boolean> {
|
||||
const originalY: number = pokemon.y;
|
||||
|
||||
if (!ballTwitchRate) {
|
||||
|
@ -397,7 +463,9 @@ export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: Pokeba
|
|||
});
|
||||
|
||||
return new Promise(resolve => {
|
||||
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
|
||||
globalScene.trainer.setTexture(
|
||||
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`,
|
||||
);
|
||||
globalScene.time.delayedCall(512, () => {
|
||||
globalScene.playSound("se/pb_throw");
|
||||
|
||||
|
@ -406,7 +474,9 @@ export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: Pokeba
|
|||
globalScene.time.delayedCall(256, () => {
|
||||
globalScene.trainer.setFrame("3");
|
||||
globalScene.time.delayedCall(768, () => {
|
||||
globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`);
|
||||
globalScene.trainer.setTexture(
|
||||
`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -486,22 +556,22 @@ export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: Pokeba
|
|||
alpha: 0,
|
||||
duration: 200,
|
||||
easing: "Sine.easeIn",
|
||||
onComplete: () => pbTint.destroy()
|
||||
onComplete: () => pbTint.destroy(),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
onComplete: () => {
|
||||
catchPokemon(pokemon, pokeball, pokeballType).then(() => resolve(true));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
globalScene.time.delayedCall(250, () => doPokeballBounceAnim(pokeball, 16, 72, 350, doShake));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -515,7 +585,12 @@ export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: Pokeba
|
|||
* @param pokeball
|
||||
* @param pokeballType
|
||||
*/
|
||||
function failCatch(pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType) {
|
||||
function failCatch(
|
||||
pokemon: EnemyPokemon,
|
||||
originalY: number,
|
||||
pokeball: Phaser.GameObjects.Sprite,
|
||||
pokeballType: PokeballType,
|
||||
) {
|
||||
return new Promise<void>(resolve => {
|
||||
globalScene.playSound("se/pb_rel");
|
||||
pokemon.setY(originalY);
|
||||
|
@ -534,13 +609,21 @@ function failCatch(pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.Ga
|
|||
targets: pokemon,
|
||||
duration: 250,
|
||||
ease: "Sine.easeOut",
|
||||
scale: 1
|
||||
scale: 1,
|
||||
});
|
||||
|
||||
globalScene.currentBattle.lastUsedPokeball = pokeballType;
|
||||
removePb(pokeball);
|
||||
|
||||
globalScene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.getNameToRender() }), null, () => resolve(), null, true);
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:pokemonBrokeFree", {
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
}),
|
||||
null,
|
||||
() => resolve(),
|
||||
null,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -553,10 +636,19 @@ function failCatch(pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.Ga
|
|||
* @param showCatchObtainMessage
|
||||
* @param isObtain
|
||||
*/
|
||||
export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite | null, pokeballType: PokeballType, showCatchObtainMessage: boolean = true, isObtain: boolean = false): Promise<void> {
|
||||
export async function catchPokemon(
|
||||
pokemon: EnemyPokemon,
|
||||
pokeball: Phaser.GameObjects.Sprite | null,
|
||||
pokeballType: PokeballType,
|
||||
showCatchObtainMessage = true,
|
||||
isObtain = false,
|
||||
): Promise<void> {
|
||||
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
|
||||
|
||||
if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) {
|
||||
if (
|
||||
speciesForm.abilityHidden &&
|
||||
(pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1
|
||||
) {
|
||||
globalScene.validateAchv(achvs.HIDDEN_ABILITY);
|
||||
}
|
||||
|
||||
|
@ -611,35 +703,70 @@ 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) {
|
||||
const promptRelease = () => {
|
||||
globalScene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => {
|
||||
globalScene.pokemonInfoContainer.makeRoomForConfirmUi(1, true);
|
||||
globalScene.ui.setMode(Mode.CONFIRM, () => {
|
||||
const newPokemon = globalScene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon);
|
||||
globalScene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => {
|
||||
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
promptRelease();
|
||||
});
|
||||
}, false);
|
||||
}, () => {
|
||||
globalScene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: number, _option: PartyOption) => {
|
||||
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
if (slotIndex < 6) {
|
||||
addToParty(slotIndex);
|
||||
} else {
|
||||
promptRelease();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, () => {
|
||||
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
removePokemon();
|
||||
end();
|
||||
});
|
||||
}, "fullParty");
|
||||
});
|
||||
globalScene.ui.showText(
|
||||
i18next.t("battle:partyFull", {
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
}),
|
||||
null,
|
||||
() => {
|
||||
globalScene.pokemonInfoContainer.makeRoomForConfirmUi(1, true);
|
||||
globalScene.ui.setMode(
|
||||
Mode.CONFIRM,
|
||||
() => {
|
||||
const newPokemon = globalScene.addPlayerPokemon(
|
||||
pokemon.species,
|
||||
pokemon.level,
|
||||
pokemon.abilityIndex,
|
||||
pokemon.formIndex,
|
||||
pokemon.gender,
|
||||
pokemon.shiny,
|
||||
pokemon.variant,
|
||||
pokemon.ivs,
|
||||
pokemon.nature,
|
||||
pokemon,
|
||||
);
|
||||
globalScene.ui.setMode(
|
||||
Mode.SUMMARY,
|
||||
newPokemon,
|
||||
0,
|
||||
SummaryUiMode.DEFAULT,
|
||||
() => {
|
||||
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
promptRelease();
|
||||
});
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
() => {
|
||||
globalScene.ui.setMode(
|
||||
Mode.PARTY,
|
||||
PartyUiMode.RELEASE,
|
||||
0,
|
||||
(slotIndex: number, _option: PartyOption) => {
|
||||
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
if (slotIndex < 6) {
|
||||
addToParty(slotIndex);
|
||||
} else {
|
||||
promptRelease();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
() => {
|
||||
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
removePokemon();
|
||||
end();
|
||||
});
|
||||
},
|
||||
"fullParty",
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
promptRelease();
|
||||
} else {
|
||||
|
@ -649,7 +776,15 @@ export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameO
|
|||
};
|
||||
|
||||
if (showCatchObtainMessage) {
|
||||
globalScene.ui.showText(i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", { pokemonName: pokemon.getNameToRender() }), null, doPokemonCatchMenu, 0, true);
|
||||
globalScene.ui.showText(
|
||||
i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", {
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
}),
|
||||
null,
|
||||
doPokemonCatchMenu,
|
||||
0,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
doPokemonCatchMenu();
|
||||
}
|
||||
|
@ -671,7 +806,7 @@ function removePb(pokeball: Phaser.GameObjects.Sprite) {
|
|||
alpha: 0,
|
||||
onComplete: () => {
|
||||
pokeball.destroy();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -696,11 +831,17 @@ export async function doPokemonFlee(pokemon: EnemyPokemon): Promise<void> {
|
|||
onComplete: () => {
|
||||
pokemon.setVisible(false);
|
||||
pokemon.leaveField(true, true, true);
|
||||
showEncounterText(i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false)
|
||||
.then(() => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
showEncounterText(
|
||||
i18next.t("battle:pokemonFled", {
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
}),
|
||||
null,
|
||||
600,
|
||||
false,
|
||||
).then(() => {
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -724,11 +865,17 @@ export function doPlayerFlee(pokemon: EnemyPokemon): Promise<void> {
|
|||
onComplete: () => {
|
||||
pokemon.setVisible(false);
|
||||
pokemon.leaveField(true, true, true);
|
||||
showEncounterText(i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false)
|
||||
.then(() => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
showEncounterText(
|
||||
i18next.t("battle:playerFled", {
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
}),
|
||||
null,
|
||||
600,
|
||||
false,
|
||||
).then(() => {
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -737,33 +884,33 @@ export function doPlayerFlee(pokemon: EnemyPokemon): Promise<void> {
|
|||
* Bug Species and their corresponding weights
|
||||
*/
|
||||
const GOLDEN_BUG_NET_SPECIES_POOL: [Species, number][] = [
|
||||
[ Species.SCYTHER, 40 ],
|
||||
[ Species.SCIZOR, 40 ],
|
||||
[ Species.KLEAVOR, 40 ],
|
||||
[ Species.PINSIR, 40 ],
|
||||
[ Species.HERACROSS, 40 ],
|
||||
[ Species.YANMA, 40 ],
|
||||
[ Species.YANMEGA, 40 ],
|
||||
[ Species.SHUCKLE, 40 ],
|
||||
[ Species.ANORITH, 40 ],
|
||||
[ Species.ARMALDO, 40 ],
|
||||
[ Species.ESCAVALIER, 40 ],
|
||||
[ Species.ACCELGOR, 40 ],
|
||||
[ Species.JOLTIK, 40 ],
|
||||
[ Species.GALVANTULA, 40 ],
|
||||
[ Species.DURANT, 40 ],
|
||||
[ Species.LARVESTA, 40 ],
|
||||
[ Species.VOLCARONA, 40 ],
|
||||
[ Species.DEWPIDER, 40 ],
|
||||
[ Species.ARAQUANID, 40 ],
|
||||
[ Species.WIMPOD, 40 ],
|
||||
[ Species.GOLISOPOD, 40 ],
|
||||
[ Species.SIZZLIPEDE, 40 ],
|
||||
[ Species.CENTISKORCH, 40 ],
|
||||
[ Species.NYMBLE, 40 ],
|
||||
[ Species.LOKIX, 40 ],
|
||||
[ Species.BUZZWOLE, 1 ],
|
||||
[ Species.PHEROMOSA, 1 ],
|
||||
[Species.SCYTHER, 40],
|
||||
[Species.SCIZOR, 40],
|
||||
[Species.KLEAVOR, 40],
|
||||
[Species.PINSIR, 40],
|
||||
[Species.HERACROSS, 40],
|
||||
[Species.YANMA, 40],
|
||||
[Species.YANMEGA, 40],
|
||||
[Species.SHUCKLE, 40],
|
||||
[Species.ANORITH, 40],
|
||||
[Species.ARMALDO, 40],
|
||||
[Species.ESCAVALIER, 40],
|
||||
[Species.ACCELGOR, 40],
|
||||
[Species.JOLTIK, 40],
|
||||
[Species.GALVANTULA, 40],
|
||||
[Species.DURANT, 40],
|
||||
[Species.LARVESTA, 40],
|
||||
[Species.VOLCARONA, 40],
|
||||
[Species.DEWPIDER, 40],
|
||||
[Species.ARAQUANID, 40],
|
||||
[Species.WIMPOD, 40],
|
||||
[Species.GOLISOPOD, 40],
|
||||
[Species.SIZZLIPEDE, 40],
|
||||
[Species.CENTISKORCH, 40],
|
||||
[Species.NYMBLE, 40],
|
||||
[Species.LOKIX, 40],
|
||||
[Species.BUZZWOLE, 1],
|
||||
[Species.PHEROMOSA, 1],
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -792,7 +939,7 @@ export function getGoldenBugNetSpecies(level: number): PokemonSpecies {
|
|||
* @param scene
|
||||
* @param levelAdditiveModifier Default 0. will add +(1 level / 10 waves * levelAdditiveModifier) to the level calculation
|
||||
*/
|
||||
export function getEncounterPokemonLevelForWave(levelAdditiveModifier: number = 0) {
|
||||
export function getEncounterPokemonLevelForWave(levelAdditiveModifier = 0) {
|
||||
const currentBattle = globalScene.currentBattle;
|
||||
const baseLevel = currentBattle.getLevelForWave();
|
||||
|
||||
|
@ -803,7 +950,10 @@ export function getEncounterPokemonLevelForWave(levelAdditiveModifier: number =
|
|||
export async function addPokemonDataToDexAndValidateAchievements(pokemon: PlayerPokemon) {
|
||||
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
|
||||
|
||||
if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) {
|
||||
if (
|
||||
speciesForm.abilityHidden &&
|
||||
(pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1
|
||||
) {
|
||||
globalScene.validateAchv(achvs.HIDDEN_ABILITY);
|
||||
}
|
||||
|
||||
|
@ -832,9 +982,16 @@ export async function addPokemonDataToDexAndValidateAchievements(pokemon: Player
|
|||
* @param scene
|
||||
* @param invalidSelectionKey
|
||||
*/
|
||||
export function isPokemonValidForEncounterOptionSelection(pokemon: Pokemon, invalidSelectionKey: string): string | null {
|
||||
export function isPokemonValidForEncounterOptionSelection(
|
||||
pokemon: Pokemon,
|
||||
invalidSelectionKey: string,
|
||||
): string | null {
|
||||
if (!pokemon.isAllowedInChallenge()) {
|
||||
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
|
||||
return (
|
||||
i18next.t("partyUiHandler:cantBeUsed", {
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
}) ?? null
|
||||
);
|
||||
}
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return getEncounterText(invalidSelectionKey) ?? null;
|
||||
|
|
|
@ -7,7 +7,7 @@ import { globalScene } from "#app/global-scene";
|
|||
export enum TransformationScreenPosition {
|
||||
CENTER,
|
||||
LEFT,
|
||||
RIGHT
|
||||
RIGHT,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,7 +17,11 @@ export enum TransformationScreenPosition {
|
|||
* @param transformPokemon
|
||||
* @param screenPosition
|
||||
*/
|
||||
export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon, transformPokemon: PlayerPokemon, screenPosition: TransformationScreenPosition) {
|
||||
export function doPokemonTransformationSequence(
|
||||
previousPokemon: PlayerPokemon,
|
||||
transformPokemon: PlayerPokemon,
|
||||
screenPosition: TransformationScreenPosition,
|
||||
) {
|
||||
return new Promise<void>(resolve => {
|
||||
const transformationContainer = globalScene.fieldUI.getByName("Dream Background") as Phaser.GameObjects.Container;
|
||||
const transformationBaseBg = globalScene.add.image(0, 0, "default_bg");
|
||||
|
@ -30,14 +34,26 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
|
|||
let pokemonEvoSprite: Phaser.GameObjects.Sprite;
|
||||
let pokemonEvoTintSprite: Phaser.GameObjects.Sprite;
|
||||
|
||||
const xOffset = screenPosition === TransformationScreenPosition.CENTER ? 0 :
|
||||
screenPosition === TransformationScreenPosition.RIGHT ? 100 : -100;
|
||||
const xOffset =
|
||||
screenPosition === TransformationScreenPosition.CENTER
|
||||
? 0
|
||||
: screenPosition === TransformationScreenPosition.RIGHT
|
||||
? 100
|
||||
: -100;
|
||||
// Centered transformations occur at a lower y Position
|
||||
const yOffset = screenPosition !== TransformationScreenPosition.CENTER ? -15 : 0;
|
||||
|
||||
const getPokemonSprite = () => {
|
||||
const ret = globalScene.addPokemonSprite(previousPokemon, transformationBaseBg.displayWidth / 2 + xOffset, transformationBaseBg.displayHeight / 2 + yOffset, "pkmn__sub");
|
||||
ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
|
||||
const ret = globalScene.addPokemonSprite(
|
||||
previousPokemon,
|
||||
transformationBaseBg.displayWidth / 2 + xOffset,
|
||||
transformationBaseBg.displayHeight / 2 + yOffset,
|
||||
"pkmn__sub",
|
||||
);
|
||||
ret.setPipeline(globalScene.spritePipeline, {
|
||||
tone: [0.0, 0.0, 0.0, 0.0],
|
||||
ignoreTimeTint: true,
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
@ -48,12 +64,12 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
|
|||
|
||||
pokemonSprite.setAlpha(0);
|
||||
pokemonTintSprite.setAlpha(0);
|
||||
pokemonTintSprite.setTintFill(0xFFFFFF);
|
||||
pokemonTintSprite.setTintFill(0xffffff);
|
||||
pokemonEvoSprite.setVisible(false);
|
||||
pokemonEvoTintSprite.setVisible(false);
|
||||
pokemonEvoTintSprite.setTintFill(0xFFFFFF);
|
||||
pokemonEvoTintSprite.setTintFill(0xffffff);
|
||||
|
||||
[ pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
|
||||
[pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite].map(sprite => {
|
||||
const spriteKey = previousPokemon.getSpriteKey(true);
|
||||
try {
|
||||
sprite.play(spriteKey);
|
||||
|
@ -61,12 +77,17 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
|
|||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
|
||||
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()), isTerastallized: previousPokemon.isTerastallized });
|
||||
sprite.setPipeline(globalScene.spritePipeline, {
|
||||
tone: [0.0, 0.0, 0.0, 0.0],
|
||||
hasShadow: false,
|
||||
teraColor: getTypeRgb(previousPokemon.getTeraType()),
|
||||
isTerastallized: previousPokemon.isTerastallized,
|
||||
});
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey());
|
||||
sprite.setPipelineData("shiny", previousPokemon.shiny);
|
||||
sprite.setPipelineData("variant", previousPokemon.variant);
|
||||
[ "spriteColors", "fusionSpriteColors" ].map(k => {
|
||||
["spriteColors", "fusionSpriteColors"].map(k => {
|
||||
if (previousPokemon.summonData?.speciesForm) {
|
||||
k += "Base";
|
||||
}
|
||||
|
@ -74,7 +95,7 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
|
|||
});
|
||||
});
|
||||
|
||||
[ pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
|
||||
[pokemonEvoSprite, pokemonEvoTintSprite].map(sprite => {
|
||||
const spriteKey = transformPokemon.getSpriteKey(true);
|
||||
try {
|
||||
sprite.play(spriteKey);
|
||||
|
@ -86,7 +107,7 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
|
|||
sprite.setPipelineData("spriteKey", transformPokemon.getSpriteKey());
|
||||
sprite.setPipelineData("shiny", transformPokemon.shiny);
|
||||
sprite.setPipelineData("variant", transformPokemon.variant);
|
||||
[ "spriteColors", "fusionSpriteColors" ].map(k => {
|
||||
["spriteColors", "fusionSpriteColors"].map(k => {
|
||||
if (transformPokemon.summonData?.speciesForm) {
|
||||
k += "Base";
|
||||
}
|
||||
|
@ -139,18 +160,18 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
|
|||
previousPokemon.destroy();
|
||||
transformPokemon.setVisible(false);
|
||||
transformPokemon.setAlpha(1);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -163,7 +184,12 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
|
|||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doSpiralUpward(transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
function doSpiralUpward(
|
||||
transformationBaseBg: Phaser.GameObjects.Image,
|
||||
transformationContainer: Phaser.GameObjects.Container,
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
) {
|
||||
let f = 0;
|
||||
|
||||
globalScene.tweens.addCounter({
|
||||
|
@ -173,12 +199,18 @@ function doSpiralUpward(transformationBaseBg: Phaser.GameObjects.Image, transfor
|
|||
if (f < 64) {
|
||||
if (!(f & 7)) {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
doSpiralUpwardParticle((f & 120) * 2 + i * 64, transformationBaseBg, transformationContainer, xOffset, yOffset);
|
||||
doSpiralUpwardParticle(
|
||||
(f & 120) * 2 + i * 64,
|
||||
transformationBaseBg,
|
||||
transformationContainer,
|
||||
xOffset,
|
||||
yOffset,
|
||||
);
|
||||
}
|
||||
}
|
||||
f++;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -190,7 +222,12 @@ function doSpiralUpward(transformationBaseBg: Phaser.GameObjects.Image, transfor
|
|||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doArcDownward(transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
function doArcDownward(
|
||||
transformationBaseBg: Phaser.GameObjects.Image,
|
||||
transformationContainer: Phaser.GameObjects.Container,
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
) {
|
||||
let f = 0;
|
||||
|
||||
globalScene.tweens.addCounter({
|
||||
|
@ -205,7 +242,7 @@ function doArcDownward(transformationBaseBg: Phaser.GameObjects.Image, transform
|
|||
}
|
||||
f++;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -217,7 +254,12 @@ function doArcDownward(transformationBaseBg: Phaser.GameObjects.Image, transform
|
|||
* @param pokemonTintSprite
|
||||
* @param pokemonEvoTintSprite
|
||||
*/
|
||||
function doCycle(l: number, lastCycle: number, pokemonTintSprite: Phaser.GameObjects.Sprite, pokemonEvoTintSprite: Phaser.GameObjects.Sprite): Promise<boolean> {
|
||||
function doCycle(
|
||||
l: number,
|
||||
lastCycle: number,
|
||||
pokemonTintSprite: Phaser.GameObjects.Sprite,
|
||||
pokemonEvoTintSprite: Phaser.GameObjects.Sprite,
|
||||
): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const isLastCycle = l === lastCycle;
|
||||
globalScene.tweens.add({
|
||||
|
@ -225,7 +267,7 @@ function doCycle(l: number, lastCycle: number, pokemonTintSprite: Phaser.GameObj
|
|||
scale: 0.25,
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: 500 / l,
|
||||
yoyo: !isLastCycle
|
||||
yoyo: !isLastCycle,
|
||||
});
|
||||
globalScene.tweens.add({
|
||||
targets: pokemonEvoTintSprite,
|
||||
|
@ -240,7 +282,7 @@ function doCycle(l: number, lastCycle: number, pokemonTintSprite: Phaser.GameObj
|
|||
pokemonTintSprite.setVisible(false);
|
||||
resolve(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -253,7 +295,12 @@ function doCycle(l: number, lastCycle: number, pokemonTintSprite: Phaser.GameObj
|
|||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doCircleInward(transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
function doCircleInward(
|
||||
transformationBaseBg: Phaser.GameObjects.Image,
|
||||
transformationContainer: Phaser.GameObjects.Container,
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
) {
|
||||
let f = 0;
|
||||
|
||||
globalScene.tweens.addCounter({
|
||||
|
@ -270,7 +317,7 @@ function doCircleInward(transformationBaseBg: Phaser.GameObjects.Image, transfor
|
|||
}
|
||||
}
|
||||
f++;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -283,7 +330,13 @@ function doCircleInward(transformationBaseBg: Phaser.GameObjects.Image, transfor
|
|||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doSpiralUpwardParticle(trigIndex: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
function doSpiralUpwardParticle(
|
||||
trigIndex: number,
|
||||
transformationBaseBg: Phaser.GameObjects.Image,
|
||||
transformationContainer: Phaser.GameObjects.Container,
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
) {
|
||||
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
|
||||
const particle = globalScene.add.image(initialX, 0, "evo_sparkle");
|
||||
transformationContainer.add(particle);
|
||||
|
@ -296,7 +349,7 @@ function doSpiralUpwardParticle(trigIndex: number, transformationBaseBg: Phaser.
|
|||
duration: getFrameMs(1),
|
||||
onRepeat: () => {
|
||||
updateParticle();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const updateParticle = () => {
|
||||
|
@ -304,7 +357,7 @@ function doSpiralUpwardParticle(trigIndex: number, transformationBaseBg: Phaser.
|
|||
particle.setPosition(initialX, 88 - (f * f) / 80 + yOffset);
|
||||
particle.y += sin(trigIndex, amp) / 4;
|
||||
particle.x += cos(trigIndex, amp);
|
||||
particle.setScale(1 - (f / 80));
|
||||
particle.setScale(1 - f / 80);
|
||||
trigIndex += 4;
|
||||
if (f & 1) {
|
||||
amp--;
|
||||
|
@ -328,7 +381,13 @@ function doSpiralUpwardParticle(trigIndex: number, transformationBaseBg: Phaser.
|
|||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doArcDownParticle(trigIndex: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
function doArcDownParticle(
|
||||
trigIndex: number,
|
||||
transformationBaseBg: Phaser.GameObjects.Image,
|
||||
transformationContainer: Phaser.GameObjects.Container,
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
) {
|
||||
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
|
||||
const particle = globalScene.add.image(initialX, 0, "evo_sparkle");
|
||||
particle.setScale(0.5);
|
||||
|
@ -342,7 +401,7 @@ function doArcDownParticle(trigIndex: number, transformationBaseBg: Phaser.GameO
|
|||
duration: getFrameMs(1),
|
||||
onRepeat: () => {
|
||||
updateParticle();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const updateParticle = () => {
|
||||
|
@ -371,7 +430,14 @@ function doArcDownParticle(trigIndex: number, transformationBaseBg: Phaser.GameO
|
|||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
function doCircleInwardParticle(trigIndex: number, speed: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) {
|
||||
function doCircleInwardParticle(
|
||||
trigIndex: number,
|
||||
speed: number,
|
||||
transformationBaseBg: Phaser.GameObjects.Image,
|
||||
transformationContainer: Phaser.GameObjects.Container,
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
) {
|
||||
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
|
||||
const initialY = transformationBaseBg.displayHeight / 2 + yOffset;
|
||||
const particle = globalScene.add.image(initialX, initialY, "evo_sparkle");
|
||||
|
@ -384,7 +450,7 @@ function doCircleInwardParticle(trigIndex: number, speed: number, transformation
|
|||
duration: getFrameMs(1),
|
||||
onRepeat: () => {
|
||||
updateParticle();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const updateParticle = () => {
|
||||
|
|
|
@ -5,11 +5,17 @@ import { UiTheme } from "#enums/ui-theme";
|
|||
import i18next from "i18next";
|
||||
import { Stat, EFFECTIVE_STATS, getShortenedStatKey } from "#enums/stat";
|
||||
|
||||
export function getNatureName(nature: Nature, includeStatEffects: boolean = false, forStarterSelect: boolean = false, ignoreBBCode: boolean = false, uiTheme: UiTheme = UiTheme.DEFAULT): string {
|
||||
export function getNatureName(
|
||||
nature: Nature,
|
||||
includeStatEffects = false,
|
||||
forStarterSelect = false,
|
||||
ignoreBBCode = false,
|
||||
uiTheme: UiTheme = UiTheme.DEFAULT,
|
||||
): string {
|
||||
let ret = Utils.toReadableString(Nature[nature]);
|
||||
//Translating nature
|
||||
if (i18next.exists("nature:" + ret)) {
|
||||
ret = i18next.t("nature:" + ret as any);
|
||||
if (i18next.exists(`nature:${ret}`)) {
|
||||
ret = i18next.t(`nature:${ret}` as any);
|
||||
}
|
||||
if (includeStatEffects) {
|
||||
let increasedStat: Stat | null = null;
|
||||
|
@ -23,7 +29,9 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
|
|||
}
|
||||
}
|
||||
const textStyle = forStarterSelect ? TextStyle.SUMMARY_ALT : TextStyle.WINDOW;
|
||||
const getTextFrag = !ignoreBBCode ? (text: string, style: TextStyle) => getBBCodeFrag(text, style, uiTheme) : (text: string, style: TextStyle) => text;
|
||||
const getTextFrag = !ignoreBBCode
|
||||
? (text: string, style: TextStyle) => getBBCodeFrag(text, style, uiTheme)
|
||||
: (text: string, _style: TextStyle) => text;
|
||||
if (increasedStat && decreasedStat) {
|
||||
ret = `${getTextFrag(`${ret}${!forStarterSelect ? "\n" : " "}(`, textStyle)}${getTextFrag(`+${i18next.t(getShortenedStatKey(increasedStat))}`, TextStyle.SUMMARY_PINK)}${getTextFrag("/", textStyle)}${getTextFrag(`-${i18next.t(getShortenedStatKey(decreasedStat))}`, TextStyle.SUMMARY_BLUE)}${getTextFrag(")", textStyle)}`;
|
||||
} else {
|
||||
|
|
|
@ -95,16 +95,31 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number {
|
|||
const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr);
|
||||
const catchingCharmMultiplier = new NumberHolder(1);
|
||||
globalScene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier);
|
||||
const dexMultiplier = globalScene.gameMode.isDaily || dexCount > 800 ? 2.5
|
||||
: dexCount > 600 ? 2
|
||||
: dexCount > 400 ? 1.5
|
||||
: dexCount > 200 ? 1
|
||||
: dexCount > 100 ? 0.5
|
||||
: 0;
|
||||
return Math.floor(catchingCharmMultiplier.value * dexMultiplier * Math.min(255, modifiedCatchRate) / 6);
|
||||
const dexMultiplier =
|
||||
globalScene.gameMode.isDaily || dexCount > 800
|
||||
? 2.5
|
||||
: dexCount > 600
|
||||
? 2
|
||||
: dexCount > 400
|
||||
? 1.5
|
||||
: dexCount > 200
|
||||
? 1
|
||||
: dexCount > 100
|
||||
? 0.5
|
||||
: 0;
|
||||
return Math.floor((catchingCharmMultiplier.value * dexMultiplier * Math.min(255, modifiedCatchRate)) / 6);
|
||||
}
|
||||
|
||||
export function doPokeballBounceAnim(pokeball: Phaser.GameObjects.Sprite, y1: number, y2: number, baseBounceDuration: number, callback: Function, isCritical: boolean = false) {
|
||||
// TODO: fix Function annotations
|
||||
export function doPokeballBounceAnim(
|
||||
pokeball: Phaser.GameObjects.Sprite,
|
||||
y1: number,
|
||||
y2: number,
|
||||
baseBounceDuration: number,
|
||||
// biome-ignore lint/complexity/noBannedTypes: TODO
|
||||
callback: Function,
|
||||
isCritical = false,
|
||||
) {
|
||||
let bouncePower = 1;
|
||||
let bounceYOffset = y1;
|
||||
let bounceY = y2;
|
||||
|
@ -134,12 +149,12 @@ export function doPokeballBounceAnim(pokeball: Phaser.GameObjects.Sprite, y1: nu
|
|||
y: bounceY,
|
||||
duration: bouncePower * baseBounceDuration,
|
||||
ease: "Cubic.easeOut",
|
||||
onComplete: () => doBounce()
|
||||
onComplete: () => doBounce(),
|
||||
});
|
||||
} else if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -165,12 +180,12 @@ export function doPokeballBounceAnim(pokeball: Phaser.GameObjects.Sprite, y1: nu
|
|||
x: x0,
|
||||
duration: 60,
|
||||
ease: "Linear",
|
||||
onComplete: () => globalScene.time.delayedCall(500, doBounce)
|
||||
onComplete: () => globalScene.time.delayedCall(500, doBounce),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ export enum FormChangeItem {
|
|||
DRACO_PLATE,
|
||||
DREAD_PLATE,
|
||||
PIXIE_PLATE,
|
||||
BLANK_PLATE, // TODO: Find a potential use for this
|
||||
BLANK_PLATE, // TODO: Find a potential use for this
|
||||
LEGEND_PLATE, // TODO: Find a potential use for this
|
||||
FIGHTING_MEMORY,
|
||||
FLYING_MEMORY,
|
||||
|
@ -132,7 +132,7 @@ export enum FormChangeItem {
|
|||
DRAGON_MEMORY,
|
||||
DARK_MEMORY,
|
||||
FAIRY_MEMORY,
|
||||
NORMAL_MEMORY // TODO: Find a potential use for this
|
||||
NORMAL_MEMORY, // TODO: Find a potential use for this
|
||||
}
|
||||
|
||||
export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
|
||||
|
@ -146,7 +146,14 @@ export class SpeciesFormChange {
|
|||
public quiet: boolean;
|
||||
public readonly conditions: SpeciesFormChangeCondition[];
|
||||
|
||||
constructor(speciesId: Species, preFormKey: string, evoFormKey: string, trigger: SpeciesFormChangeTrigger, quiet: boolean = false, ...conditions: SpeciesFormChangeCondition[]) {
|
||||
constructor(
|
||||
speciesId: Species,
|
||||
preFormKey: string,
|
||||
evoFormKey: string,
|
||||
trigger: SpeciesFormChangeTrigger,
|
||||
quiet = false,
|
||||
...conditions: SpeciesFormChangeCondition[]
|
||||
) {
|
||||
this.speciesId = speciesId;
|
||||
this.preFormKey = preFormKey;
|
||||
this.formKey = evoFormKey;
|
||||
|
@ -212,9 +219,9 @@ export class SpeciesFormChangeCondition {
|
|||
}
|
||||
|
||||
export abstract class SpeciesFormChangeTrigger {
|
||||
public description: string = "";
|
||||
public description = "";
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
canChange(_pokemon: Pokemon): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -223,20 +230,22 @@ export abstract class SpeciesFormChangeTrigger {
|
|||
}
|
||||
}
|
||||
|
||||
export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {
|
||||
}
|
||||
export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {}
|
||||
|
||||
export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger {
|
||||
public description: string = i18next.t("pokemonEvolutions:Forms.ability");
|
||||
}
|
||||
|
||||
export class SpeciesFormChangeCompoundTrigger {
|
||||
public description: string = "";
|
||||
public description = "";
|
||||
public triggers: SpeciesFormChangeTrigger[];
|
||||
|
||||
constructor(...triggers: SpeciesFormChangeTrigger[]) {
|
||||
this.triggers = triggers;
|
||||
this.description = this.triggers.filter(trigger => trigger?.description?.length > 0).map(trigger => trigger.description).join(", ");
|
||||
this.description = this.triggers
|
||||
.filter(trigger => trigger?.description?.length > 0)
|
||||
.map(trigger => trigger.description)
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
|
@ -258,17 +267,27 @@ export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger {
|
|||
public item: FormChangeItem;
|
||||
public active: boolean;
|
||||
|
||||
constructor(item: FormChangeItem, active: boolean = true) {
|
||||
constructor(item: FormChangeItem, active = true) {
|
||||
super();
|
||||
this.item = item;
|
||||
this.active = active;
|
||||
this.description = this.active ?
|
||||
i18next.t("pokemonEvolutions:Forms.item", { item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`) }) :
|
||||
i18next.t("pokemonEvolutions:Forms.deactivateItem", { item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`) });
|
||||
this.description = this.active
|
||||
? i18next.t("pokemonEvolutions:Forms.item", {
|
||||
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
|
||||
})
|
||||
: i18next.t("pokemonEvolutions:Forms.deactivateItem", {
|
||||
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
|
||||
});
|
||||
}
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
return !!globalScene.findModifier(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id && m.formChangeItem === this.item && m.active === this.active);
|
||||
return !!globalScene.findModifier(
|
||||
m =>
|
||||
m instanceof PokemonFormChangeItemModifier &&
|
||||
m.pokemonId === pokemon.id &&
|
||||
m.formChangeItem === this.item &&
|
||||
m.active === this.active,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -281,7 +300,7 @@ export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger
|
|||
this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay");
|
||||
}
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
canChange(_pokemon: Pokemon): boolean {
|
||||
return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1;
|
||||
}
|
||||
}
|
||||
|
@ -289,10 +308,12 @@ export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger
|
|||
export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger {
|
||||
public active: boolean;
|
||||
|
||||
constructor(active: boolean = false) {
|
||||
constructor(active = false) {
|
||||
super();
|
||||
this.active = active;
|
||||
this.description = this.active ? i18next.t("pokemonEvolutions:Forms.enter") : i18next.t("pokemonEvolutions:Forms.leave");
|
||||
this.description = this.active
|
||||
? i18next.t("pokemonEvolutions:Forms.enter")
|
||||
: i18next.t("pokemonEvolutions:Forms.leave");
|
||||
}
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
|
@ -304,10 +325,10 @@ export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigg
|
|||
public statusEffects: StatusEffect[];
|
||||
public invert: boolean;
|
||||
|
||||
constructor(statusEffects: StatusEffect | StatusEffect[], invert: boolean = false) {
|
||||
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
|
||||
super();
|
||||
if (!Array.isArray(statusEffects)) {
|
||||
statusEffects = [ statusEffects ];
|
||||
statusEffects = [statusEffects];
|
||||
}
|
||||
this.statusEffects = statusEffects;
|
||||
this.invert = invert;
|
||||
|
@ -315,7 +336,7 @@ export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigg
|
|||
}
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
return (this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1) !== this.invert;
|
||||
return this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1 !== this.invert;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,17 +344,26 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge
|
|||
public move: Moves;
|
||||
public known: boolean;
|
||||
|
||||
constructor(move: Moves, known: boolean = true) {
|
||||
constructor(move: Moves, known = true) {
|
||||
super();
|
||||
this.move = move;
|
||||
this.known = known;
|
||||
const moveKey = Moves[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("") as unknown as string;
|
||||
this.description = known ? i18next.t("pokemonEvolutions:Forms.moveLearned", { move: i18next.t(`move:${moveKey}.name`) }) :
|
||||
i18next.t("pokemonEvolutions:Forms.moveForgotten", { move: i18next.t(`move:${moveKey}.name`) });
|
||||
const moveKey = Moves[this.move]
|
||||
.split("_")
|
||||
.filter(f => f)
|
||||
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
|
||||
.join("") as unknown as string;
|
||||
this.description = known
|
||||
? i18next.t("pokemonEvolutions:Forms.moveLearned", {
|
||||
move: i18next.t(`move:${moveKey}.name`),
|
||||
})
|
||||
: i18next.t("pokemonEvolutions:Forms.moveForgotten", {
|
||||
move: i18next.t(`move:${moveKey}.name`),
|
||||
});
|
||||
}
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
return (!!pokemon.moveset.filter(m => m?.moveId === this.move).length) === this.known;
|
||||
return !!pokemon.moveset.filter(m => m?.moveId === this.move).length === this.known;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,7 +371,7 @@ export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrig
|
|||
public movePredicate: (m: Moves) => boolean;
|
||||
public used: boolean;
|
||||
|
||||
constructor(move: Moves | ((m: Moves) => boolean), used: boolean = true) {
|
||||
constructor(move: Moves | ((m: Moves) => boolean), used = true) {
|
||||
super();
|
||||
this.movePredicate = typeof move === "function" ? move : (m: Moves) => m === move;
|
||||
this.used = used;
|
||||
|
@ -361,7 +391,9 @@ export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigg
|
|||
description = i18next.t("pokemonEvolutions:Forms.postMove");
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
return pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used;
|
||||
return (
|
||||
pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -369,13 +401,12 @@ export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMove
|
|||
override canChange(pokemon: Pokemon): boolean {
|
||||
if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
|
||||
return false;
|
||||
} else {
|
||||
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song
|
||||
if (pokemon.hasAbility(Abilities.SHEER_FORCE)) {
|
||||
return false;
|
||||
}
|
||||
return super.canChange(pokemon);
|
||||
}
|
||||
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song
|
||||
if (pokemon.hasAbility(Abilities.SHEER_FORCE)) {
|
||||
return false;
|
||||
}
|
||||
return super.canChange(pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,7 +420,11 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
|
|||
}
|
||||
|
||||
canChange(pokemon: Pokemon): boolean {
|
||||
return this.formKey === pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)].formKey;
|
||||
return (
|
||||
this.formKey ===
|
||||
pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)]
|
||||
.formKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -399,7 +434,7 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
|
|||
* @extends SpeciesFormChangeTrigger
|
||||
*/
|
||||
export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {
|
||||
description = i18next.t("pokemonEvolutions:Forms.tera" );
|
||||
description = i18next.t("pokemonEvolutions:Forms.tera");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -440,7 +475,12 @@ export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
|
|||
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
|
||||
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
|
||||
|
||||
return !isAbilitySuppressed && !isWeatherSuppressed && (pokemon.hasAbility(this.ability) && this.weathers.includes(currentWeather));
|
||||
return (
|
||||
!isAbilitySuppressed &&
|
||||
!isWeatherSuppressed &&
|
||||
pokemon.hasAbility(this.ability) &&
|
||||
this.weathers.includes(currentWeather)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -491,16 +531,27 @@ export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: Specie
|
|||
const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1;
|
||||
const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey;
|
||||
if (isMega) {
|
||||
return i18next.t("battlePokemonForm:megaChange", { preName, pokemonName: pokemon.name });
|
||||
return i18next.t("battlePokemonForm:megaChange", {
|
||||
preName,
|
||||
pokemonName: pokemon.name,
|
||||
});
|
||||
}
|
||||
if (isGmax) {
|
||||
return i18next.t("battlePokemonForm:gigantamaxChange", { preName, pokemonName: pokemon.name });
|
||||
return i18next.t("battlePokemonForm:gigantamaxChange", {
|
||||
preName,
|
||||
pokemonName: pokemon.name,
|
||||
});
|
||||
}
|
||||
if (isEmax) {
|
||||
return i18next.t("battlePokemonForm:eternamaxChange", { preName, pokemonName: pokemon.name });
|
||||
return i18next.t("battlePokemonForm:eternamaxChange", {
|
||||
preName,
|
||||
pokemonName: pokemon.name,
|
||||
});
|
||||
}
|
||||
if (isRevert) {
|
||||
return i18next.t("battlePokemonForm:revertChange", { pokemonName: getPokemonNameWithAffix(pokemon) });
|
||||
return i18next.t("battlePokemonForm:revertChange", {
|
||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||
});
|
||||
}
|
||||
if (pokemon.getAbility().id === Abilities.DISGUISE) {
|
||||
return i18next.t("battlePokemonForm:disguiseChange");
|
||||
|
@ -515,13 +566,14 @@ export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: Specie
|
|||
* @returns A {@linkcode SpeciesFormChangeCondition} checking if that species is registered as caught
|
||||
*/
|
||||
function getSpeciesDependentFormChangeCondition(species: Species): SpeciesFormChangeCondition {
|
||||
return new SpeciesFormChangeCondition(p => !!globalScene.gameData.dexData[species].caughtAttr);
|
||||
return new SpeciesFormChangeCondition(_p => !!globalScene.gameData.dexData[species].caughtAttr);
|
||||
}
|
||||
|
||||
interface PokemonFormChanges {
|
||||
[key: string]: SpeciesFormChange[]
|
||||
[key: string]: SpeciesFormChange[];
|
||||
}
|
||||
|
||||
// biome-ignore format: manually formatted
|
||||
export const pokemonFormChanges: PokemonFormChanges = {
|
||||
[Species.VENUSAUR]: [
|
||||
new SpeciesFormChange(Species.VENUSAUR, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.VENUSAURITE)),
|
||||
|
@ -994,16 +1046,22 @@ export const pokemonFormChanges: PokemonFormChanges = {
|
|||
|
||||
export function initPokemonForms() {
|
||||
const formChangeKeys = Object.keys(pokemonFormChanges);
|
||||
formChangeKeys.forEach(pk => {
|
||||
for (const pk of formChangeKeys) {
|
||||
const formChanges = pokemonFormChanges[pk];
|
||||
const newFormChanges: SpeciesFormChange[] = [];
|
||||
for (const fc of formChanges) {
|
||||
const itemTrigger = fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger;
|
||||
if (itemTrigger && !formChanges.find(c => fc.formKey === c.preFormKey && fc.preFormKey === c.formKey)) {
|
||||
newFormChanges.push(new SpeciesFormChange(fc.speciesId, fc.formKey, fc.preFormKey, new SpeciesFormChangeItemTrigger(itemTrigger.item, false)));
|
||||
newFormChanges.push(
|
||||
new SpeciesFormChange(
|
||||
fc.speciesId,
|
||||
fc.formKey,
|
||||
fc.preFormKey,
|
||||
new SpeciesFormChangeItemTrigger(itemTrigger.item, false),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
formChanges.push(...newFormChanges);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,10 +13,18 @@ import { uncatchableSpecies } from "#app/data/balance/biomes";
|
|||
import { speciesEggMoves } from "#app/data/balance/egg-moves";
|
||||
import { GrowthRate } from "#app/data/exp";
|
||||
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 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 { Variant, VariantSet } from "#app/data/variant";
|
||||
import { variantData } from "#app/data/variant";
|
||||
|
@ -29,7 +37,7 @@ export enum Region {
|
|||
ALOLA,
|
||||
GALAR,
|
||||
HISUI,
|
||||
PALDEA
|
||||
PALDEA,
|
||||
}
|
||||
|
||||
// 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.CRAMORANT,
|
||||
Species.ZARUDE,
|
||||
Species.CALYREX
|
||||
Species.CALYREX,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -81,9 +89,10 @@ export function getPokemonSpecies(species: Species | Species[]): PokemonSpecies
|
|||
}
|
||||
|
||||
export function getPokemonSpeciesForm(species: Species, formIndex: number): PokemonSpeciesForm {
|
||||
const retSpecies: PokemonSpecies = species >= 2000
|
||||
? allSpecies.find(s => s.speciesId === species)! // TODO: is the bang correct?
|
||||
: allSpecies[species - 1];
|
||||
const retSpecies: PokemonSpecies =
|
||||
species >= 2000
|
||||
? allSpecies.find(s => s.speciesId === species)! // TODO: is the bang correct?
|
||||
: allSpecies[species - 1];
|
||||
if (formIndex < retSpecies.forms?.length) {
|
||||
return retSpecies.forms[formIndex];
|
||||
}
|
||||
|
@ -94,8 +103,8 @@ export function getFusedSpeciesName(speciesAName: string, speciesBName: string):
|
|||
const fragAPattern = /([a-z]{2}.*?[aeiou(?:y$)\-\']+)(.*?)$/i;
|
||||
const fragBPattern = /([a-z]{2}.*?[aeiou(?:y$)\-\'])(.*?)$/i;
|
||||
|
||||
const [ speciesAPrefixMatch, speciesBPrefixMatch ] = [ speciesAName, speciesBName ].map(n => /^(?:[^ ]+) /.exec(n));
|
||||
const [ speciesAPrefix, speciesBPrefix ] = [ speciesAPrefixMatch, speciesBPrefixMatch ].map(m => m ? m[0] : "");
|
||||
const [speciesAPrefixMatch, speciesBPrefixMatch] = [speciesAName, speciesBName].map(n => /^(?:[^ ]+) /.exec(n));
|
||||
const [speciesAPrefix, speciesBPrefix] = [speciesAPrefixMatch, speciesBPrefixMatch].map(m => (m ? m[0] : ""));
|
||||
|
||||
if (speciesAPrefix) {
|
||||
speciesAName = speciesAName.slice(speciesAPrefix.length);
|
||||
|
@ -104,8 +113,8 @@ export function getFusedSpeciesName(speciesAName: string, speciesBName: string):
|
|||
speciesBName = speciesBName.slice(speciesBPrefix.length);
|
||||
}
|
||||
|
||||
const [ speciesASuffixMatch, speciesBSuffixMatch ] = [ speciesAName, speciesBName ].map(n => / (?:[^ ]+)$/.exec(n));
|
||||
const [ speciesASuffix, speciesBSuffix ] = [ speciesASuffixMatch, speciesBSuffixMatch ].map(m => m ? m[0] : "");
|
||||
const [speciesASuffixMatch, speciesBSuffixMatch] = [speciesAName, speciesBName].map(n => / (?:[^ ]+)$/.exec(n));
|
||||
const [speciesASuffix, speciesBSuffix] = [speciesASuffixMatch, speciesBSuffixMatch].map(m => (m ? m[0] : ""));
|
||||
|
||||
if (speciesASuffix) {
|
||||
speciesAName = speciesAName.slice(0, -speciesASuffix.length);
|
||||
|
@ -123,9 +132,7 @@ export function getFusedSpeciesName(speciesAName: string, speciesBName: string):
|
|||
let fragA: string;
|
||||
let fragB: string;
|
||||
|
||||
fragA = splitNameA.length === 1
|
||||
? fragAMatch ? fragAMatch[1] : speciesAName
|
||||
: splitNameA[splitNameA.length - 1];
|
||||
fragA = splitNameA.length === 1 ? (fragAMatch ? fragAMatch[1] : speciesAName) : splitNameA[splitNameA.length - 1];
|
||||
|
||||
if (splitNameB.length === 1) {
|
||||
if (fragBMatch) {
|
||||
|
@ -179,9 +186,26 @@ export abstract class PokemonSpeciesForm {
|
|||
readonly genderDiffs: boolean;
|
||||
readonly isStarterSelectable: boolean;
|
||||
|
||||
constructor(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: boolean, isStarterSelectable: boolean
|
||||
constructor(
|
||||
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: boolean,
|
||||
isStarterSelectable: boolean,
|
||||
) {
|
||||
this.type1 = type1;
|
||||
this.type2 = type2;
|
||||
|
@ -191,7 +215,7 @@ export abstract class PokemonSpeciesForm {
|
|||
this.ability2 = ability2 === Abilities.NONE ? ability1 : ability2;
|
||||
this.abilityHidden = abilityHidden;
|
||||
this.baseTotal = baseTotal;
|
||||
this.baseStats = [ baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd ];
|
||||
this.baseStats = [baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd];
|
||||
this.catchRate = catchRate;
|
||||
this.baseFriendship = baseFriendship;
|
||||
this.baseExp = baseExp;
|
||||
|
@ -206,7 +230,7 @@ export abstract class PokemonSpeciesForm {
|
|||
* @param forStarter boolean to get the nonbaby form of a starter
|
||||
* @returns The species
|
||||
*/
|
||||
getRootSpeciesId(forStarter: boolean = false): Species {
|
||||
getRootSpeciesId(forStarter = false): Species {
|
||||
let ret = this.speciesId;
|
||||
while (pokemonPrevolutions.hasOwnProperty(ret) && (!forStarter || !speciesStarterCosts.hasOwnProperty(ret))) {
|
||||
ret = pokemonPrevolutions[ret];
|
||||
|
@ -269,23 +293,29 @@ export abstract class PokemonSpeciesForm {
|
|||
formIndex = this.formIndex;
|
||||
}
|
||||
let starterSpeciesId = this.speciesId;
|
||||
while (!(starterSpeciesId in starterPassiveAbilities) || !(formIndex in starterPassiveAbilities[starterSpeciesId])) {
|
||||
while (
|
||||
!(starterSpeciesId in starterPassiveAbilities) ||
|
||||
!(formIndex in starterPassiveAbilities[starterSpeciesId])
|
||||
) {
|
||||
if (pokemonPrevolutions.hasOwnProperty(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]) {
|
||||
return starterPassiveAbilities[starterSpeciesId][0];
|
||||
} else {
|
||||
console.log("No passive ability found for %s, using run away", this.speciesId);
|
||||
return Abilities.RUN_AWAY;
|
||||
}
|
||||
console.log("No passive ability found for %s, using run away", this.speciesId);
|
||||
return Abilities.RUN_AWAY;
|
||||
}
|
||||
}
|
||||
return starterPassiveAbilities[starterSpeciesId][formIndex];
|
||||
}
|
||||
|
||||
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 pokemonSpeciesLevelMoves[this.speciesId].slice(0);
|
||||
|
@ -296,7 +326,7 @@ export abstract class PokemonSpeciesForm {
|
|||
}
|
||||
|
||||
isObtainable(): boolean {
|
||||
return (this.generation <= 9 || pokemonPrevolutions.hasOwnProperty(this.speciesId));
|
||||
return this.generation <= 9 || pokemonPrevolutions.hasOwnProperty(this.speciesId);
|
||||
}
|
||||
|
||||
isCatchable(): boolean {
|
||||
|
@ -308,7 +338,7 @@ export abstract class PokemonSpeciesForm {
|
|||
}
|
||||
|
||||
isTrainerForbidden(): boolean {
|
||||
return [ Species.ETERNAL_FLOETTE, Species.BLOODMOON_URSALUNA ].includes(this.speciesId);
|
||||
return [Species.ETERNAL_FLOETTE, Species.BLOODMOON_URSALUNA].includes(this.speciesId);
|
||||
}
|
||||
|
||||
isRareRegional(): boolean {
|
||||
|
@ -357,18 +387,19 @@ export abstract class PokemonSpeciesForm {
|
|||
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) {
|
||||
formIndex = this.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}` : ""}`;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
/**
|
||||
* Variant Data key/index is either species id or species id followed by -formkey
|
||||
* @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 {
|
||||
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" : ""}`;
|
||||
}
|
||||
|
||||
|
@ -414,7 +445,8 @@ export abstract class PokemonSpeciesForm {
|
|||
|
||||
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) {
|
||||
ret += "s";
|
||||
|
@ -479,7 +511,9 @@ export abstract class PokemonSpeciesForm {
|
|||
const forms = getPokemonSpecies(speciesId).forms;
|
||||
if (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);
|
||||
}
|
||||
const formKey = forms[formIndex || 0].formKey;
|
||||
|
@ -532,11 +566,14 @@ export abstract class PokemonSpeciesForm {
|
|||
for (const moveId of moveset) {
|
||||
if (speciesEggMoves.hasOwnProperty(rootSpeciesId)) {
|
||||
const eggMoveIndex = speciesEggMoves[rootSpeciesId].findIndex(m => m === moveId);
|
||||
if (eggMoveIndex > -1 && (eggMoves & (1 << eggMoveIndex))) {
|
||||
if (eggMoveIndex > -1 && eggMoves & (1 << eggMoveIndex)) {
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -548,7 +585,14 @@ export abstract class PokemonSpeciesForm {
|
|||
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 => {
|
||||
const spriteKey = this.getSpriteKey(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;
|
||||
// Ignore warnings for missing frames, because there will be a lot
|
||||
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;
|
||||
if (!(globalScene.anims.exists(spriteKey))) {
|
||||
if (!globalScene.anims.exists(spriteKey)) {
|
||||
globalScene.anims.create({
|
||||
key: this.getSpriteKey(female, formIndex, shiny, variant, back),
|
||||
frames: frameNames,
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
repeat: -1,
|
||||
});
|
||||
} else {
|
||||
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());
|
||||
});
|
||||
if (startLoad) {
|
||||
|
@ -618,9 +669,9 @@ export abstract class PokemonSpeciesForm {
|
|||
for (let i = 0; i < pixelData.length; i += 4) {
|
||||
if (pixelData[i + 3]) {
|
||||
const pixel = pixelData.slice(i, i + 4);
|
||||
const [ r, g, b, a ] = pixel;
|
||||
const [r, g, b, a] = pixel;
|
||||
if (!spriteColors.find(c => c[0] === r && c[1] === g && c[2] === b)) {
|
||||
spriteColors.push([ r, g, b, a ]);
|
||||
spriteColors.push([r, g, b, a]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -630,7 +681,14 @@ export abstract class PokemonSpeciesForm {
|
|||
if (!total) {
|
||||
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;
|
||||
Math.random = () => Phaser.Math.RND.realInRange(0, 1);
|
||||
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
paletteColors = QuantizerCelebi.quantize(pixelColors, 2);
|
||||
}, 0, "This result should not vary");
|
||||
globalScene.executeWithSeedOffset(
|
||||
() => {
|
||||
paletteColors = QuantizerCelebi.quantize(pixelColors, 2);
|
||||
},
|
||||
0,
|
||||
"This result should not vary",
|
||||
);
|
||||
|
||||
Math.random = originalRandom;
|
||||
|
||||
|
@ -661,14 +723,57 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||
readonly canChangeForm: boolean;
|
||||
readonly forms: PokemonForm[];
|
||||
|
||||
constructor(id: Species, generation: number, subLegendary: boolean, 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[]
|
||||
constructor(
|
||||
id: Species,
|
||||
generation: number,
|
||||
subLegendary: boolean,
|
||||
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,
|
||||
catchRate, baseFriendship, baseExp, genderDiffs, false);
|
||||
super(
|
||||
type1,
|
||||
type2,
|
||||
height,
|
||||
weight,
|
||||
ability1,
|
||||
ability2,
|
||||
abilityHidden,
|
||||
baseTotal,
|
||||
baseHp,
|
||||
baseAtk,
|
||||
baseDef,
|
||||
baseSpatk,
|
||||
baseSpdef,
|
||||
baseSpd,
|
||||
catchRate,
|
||||
baseFriendship,
|
||||
baseExp,
|
||||
genderDiffs,
|
||||
false,
|
||||
);
|
||||
this.speciesId = id;
|
||||
this.formIndex = 0;
|
||||
this.generation = generation;
|
||||
|
@ -712,7 +817,9 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||
}
|
||||
|
||||
if (key) {
|
||||
return i18next.t(`battlePokemonForm:${key}`, { pokemonName: this.name });
|
||||
return i18next.t(`battlePokemonForm:${key}`, {
|
||||
pokemonName: this.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
return this.name;
|
||||
|
@ -725,29 +832,47 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||
getExpandedSpeciesName(): string {
|
||||
if (this.speciesId < 2000) {
|
||||
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 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the form name for species with just one form (regional variants, Floette, Ursaluna)
|
||||
* @param formIndex The form index to check (defaults to 0)
|
||||
* @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)
|
||||
*/
|
||||
getFormNameToDisplay(formIndex: number = 0, append: boolean = false): string {
|
||||
* Find the form name for species with just one form (regional variants, Floette, Ursaluna)
|
||||
* @param formIndex The form index to check (defaults to 0)
|
||||
* @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)
|
||||
*/
|
||||
getFormNameToDisplay(formIndex = 0, append = false): string {
|
||||
const formKey = this.forms?.[formIndex!]?.formKey;
|
||||
const formText = Utils.capitalizeString(formKey, "-", false, false) || "";
|
||||
const speciesName = Utils.capitalizeString(Species[this.speciesId], "_", true, false);
|
||||
let ret: string = "";
|
||||
let ret = "";
|
||||
|
||||
const region = this.getRegion();
|
||||
if (this.speciesId === Species.ARCEUS) {
|
||||
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)) {
|
||||
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
|
||||
} 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)
|
||||
) {
|
||||
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}`;
|
||||
if (i18next.exists(i18key)) {
|
||||
ret = i18next.t(i18key);
|
||||
|
@ -756,16 +881,25 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||
const i18RootKey = `pokemonForm:${rootSpeciesName}${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();
|
||||
} 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");
|
||||
} 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");
|
||||
} 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 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 {
|
||||
|
@ -773,10 +907,20 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
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 :)
|
||||
!allowEvolving
|
||||
|| !pokemonEvolutions.hasOwnProperty(this.speciesId)
|
||||
|| globalScene.currentBattle?.waveIndex === 20 && globalScene.gameMode.isClassic && globalScene.currentBattle.trainer
|
||||
if (
|
||||
// If evolutions shouldn't happen, add more cases here :)
|
||||
!allowEvolving ||
|
||||
!pokemonEvolutions.hasOwnProperty(this.speciesId) ||
|
||||
(globalScene.currentBattle?.waveIndex === 20 &&
|
||||
globalScene.gameMode.isClassic &&
|
||||
globalScene.currentBattle.trainer)
|
||||
) {
|
||||
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 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 {
|
||||
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);
|
||||
|
||||
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) {
|
||||
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 (isRegionalEvolution) {
|
||||
evolutionChance /= (evolutionSpecies.isRareRegional() ? 16 : 4);
|
||||
evolutionChance /= evolutionSpecies.isRareRegional() ? 16 : 4;
|
||||
}
|
||||
|
||||
totalWeight += evolutionChance;
|
||||
|
||||
evolutionPool.set(totalWeight, ev.speciesId);
|
||||
|
||||
if ((1 - evolutionChance) < noEvolutionChance) {
|
||||
if (1 - evolutionChance < noEvolutionChance) {
|
||||
noEvolutionChance = 1 - evolutionChance;
|
||||
}
|
||||
}
|
||||
|
@ -912,7 +1077,13 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||
for (const weight of evolutionPool.keys()) {
|
||||
if (randValue < weight) {
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -928,7 +1099,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||
for (const e of pokemonEvolutions[this.speciesId]) {
|
||||
const speciesId = e.speciesId;
|
||||
const level = e.level;
|
||||
evolutionLevels.push([ speciesId, level ]);
|
||||
evolutionLevels.push([speciesId, level]);
|
||||
//console.log(Species[speciesId], getPokemonSpecies(speciesId), getPokemonSpecies(speciesId).getEvolutionLevels());
|
||||
const nextEvolutionLevels = getPokemonSpecies(speciesId).getEvolutionLevels();
|
||||
for (const npl of nextEvolutionLevels) {
|
||||
|
@ -946,10 +1117,14 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||
const allEvolvingPokemon = Object.keys(pokemonEvolutions);
|
||||
for (const p of allEvolvingPokemon) {
|
||||
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))) {
|
||||
const speciesId = parseInt(p) as Species;
|
||||
if (
|
||||
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;
|
||||
prevolutionLevels.push([ speciesId, level ]);
|
||||
prevolutionLevels.push([speciesId, level]);
|
||||
const subPrevolutionLevels = getPokemonSpecies(speciesId).getPrevolutionLevels();
|
||||
for (const spl of subPrevolutionLevels) {
|
||||
prevolutionLevels.push(spl);
|
||||
|
@ -962,21 +1137,53 @@ 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
|
||||
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[] = [];
|
||||
if (pokemonPrevolutions.hasOwnProperty(this.speciesId)) {
|
||||
const prevolutionLevels = this.getPrevolutionLevels().reverse();
|
||||
const levelDiff = player ? 0 : forTrainer || isBoss ? forTrainer && isBoss ? 2.5 : 5 : 10;
|
||||
ret.push([ prevolutionLevels[0][0], 1 ]);
|
||||
const levelDiff = player ? 0 : forTrainer || isBoss ? (forTrainer && isBoss ? 2.5 : 5) : 10;
|
||||
ret.push([prevolutionLevels[0][0], 1]);
|
||||
for (let l = 1; l < prevolutionLevels.length; l++) {
|
||||
const evolution = pokemonEvolutions[prevolutionLevels[l - 1][0]].find(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 evolution = pokemonEvolutions[prevolutionLevels[l - 1][0]].find(
|
||||
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 evolution = pokemonEvolutions[prevolutionLevels[prevolutionLevels.length - 1][0]].find(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?
|
||||
const evolution = pokemonEvolutions[prevolutionLevels[prevolutionLevels.length - 1][0]].find(
|
||||
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 {
|
||||
ret.push([ this.speciesId, 1 ]);
|
||||
ret.push([this.speciesId, 1]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -990,19 +1197,17 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||
const mythical = this.mythical;
|
||||
return species => {
|
||||
return (
|
||||
subLegendary
|
||||
|| legendary
|
||||
|| mythical
|
||||
|| (
|
||||
pokemonEvolutions.hasOwnProperty(species.speciesId) === hasEvolution
|
||||
&& pokemonPrevolutions.hasOwnProperty(species.speciesId) === hasPrevolution
|
||||
)
|
||||
)
|
||||
&& species.subLegendary === subLegendary
|
||||
&& species.legendary === legendary
|
||||
&& species.mythical === mythical
|
||||
&& (this.isTrainerForbidden() || !species.isTrainerForbidden())
|
||||
&& species.speciesId !== Species.DITTO;
|
||||
(subLegendary ||
|
||||
legendary ||
|
||||
mythical ||
|
||||
(pokemonEvolutions.hasOwnProperty(species.speciesId) === hasEvolution &&
|
||||
pokemonPrevolutions.hasOwnProperty(species.speciesId) === hasPrevolution)) &&
|
||||
species.subLegendary === subLegendary &&
|
||||
species.legendary === legendary &&
|
||||
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) {
|
||||
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`);
|
||||
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`,
|
||||
);
|
||||
formIndex = Math.min(formIndex, this.forms.length - 1);
|
||||
}
|
||||
return this.forms?.length
|
||||
? this.forms[formIndex || 0].getFormSpriteKey()
|
||||
: "";
|
||||
return this.forms?.length ? 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}.
|
||||
*/
|
||||
getFullUnlocksData(): bigint {
|
||||
let caughtAttr: bigint = 0n;
|
||||
let caughtAttr = 0n;
|
||||
caughtAttr += DexAttr.NON_SHINY;
|
||||
caughtAttr += DexAttr.SHINY;
|
||||
if (this.malePercent !== null) {
|
||||
|
@ -1055,9 +1260,12 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||
}
|
||||
|
||||
// Summing successive bigints for each obtainable form
|
||||
caughtAttr += this?.forms?.length > 1 ?
|
||||
this.forms.map((f, index) => f.isUnobtainable ? 0n : 128n * 2n ** BigInt(index)).reduce((acc, val) => acc + val, 0n) :
|
||||
DexAttr.DEFAULT_FORM;
|
||||
caughtAttr +=
|
||||
this?.forms?.length > 1
|
||||
? this.forms
|
||||
.map((f, index) => (f.isUnobtainable ? 0n : 128n * 2n ** BigInt(index)))
|
||||
.reduce((acc, val) => acc + val, 0n)
|
||||
: DexAttr.DEFAULT_FORM;
|
||||
|
||||
return caughtAttr;
|
||||
}
|
||||
|
@ -1070,15 +1278,66 @@ export class PokemonForm extends PokemonSpeciesForm {
|
|||
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
|
||||
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,
|
||||
baseTotal: number, baseHp: number, baseAtk: number, baseDef: number, baseSpatk: number, baseSpdef: number, baseSpd: number,
|
||||
catchRate: number, baseFriendship: number, baseExp: number, genderDiffs: boolean = false, formSpriteKey: string | null = null, isStarterSelectable: boolean = false,
|
||||
isUnobtainable: boolean = false
|
||||
constructor(
|
||||
formName: string,
|
||||
formKey: 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,
|
||||
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,
|
||||
catchRate, baseFriendship, baseExp, genderDiffs, (isStarterSelectable || !formKey));
|
||||
super(
|
||||
type1,
|
||||
type2,
|
||||
height,
|
||||
weight,
|
||||
ability1,
|
||||
ability2,
|
||||
abilityHidden,
|
||||
baseTotal,
|
||||
baseHp,
|
||||
baseAtk,
|
||||
baseDef,
|
||||
baseSpatk,
|
||||
baseSpdef,
|
||||
baseSpd,
|
||||
catchRate,
|
||||
baseFriendship,
|
||||
baseExp,
|
||||
genderDiffs,
|
||||
isStarterSelectable || !formKey,
|
||||
);
|
||||
this.formName = formName;
|
||||
this.formKey = formKey;
|
||||
this.formSpriteKey = formSpriteKey;
|
||||
|
@ -1091,27 +1350,32 @@ export class PokemonForm extends PokemonSpeciesForm {
|
|||
}
|
||||
|
||||
/**
|
||||
* Method to get the daily list of starters with Pokerus.
|
||||
* @returns A list of starters with Pokerus
|
||||
*/
|
||||
* Method to get the daily list of starters with Pokerus.
|
||||
* @returns A list of starters with Pokerus
|
||||
*/
|
||||
export function getPokerusStarters(): PokemonSpecies[] {
|
||||
const pokerusStarters: PokemonSpecies[] = [];
|
||||
const date = new Date();
|
||||
date.setUTCHours(0, 0, 0, 0);
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
while (pokerusStarters.length < POKERUS_STARTER_COUNT) {
|
||||
const randomSpeciesId = parseInt(Utils.randSeedItem(Object.keys(speciesStarterCosts)), 10);
|
||||
const species = getPokemonSpecies(randomSpeciesId);
|
||||
if (!pokerusStarters.includes(species)) {
|
||||
pokerusStarters.push(species);
|
||||
globalScene.executeWithSeedOffset(
|
||||
() => {
|
||||
while (pokerusStarters.length < POKERUS_STARTER_COUNT) {
|
||||
const randomSpeciesId = Number.parseInt(Utils.randSeedItem(Object.keys(speciesStarterCosts)), 10);
|
||||
const species = getPokemonSpecies(randomSpeciesId);
|
||||
if (!pokerusStarters.includes(species)) {
|
||||
pokerusStarters.push(species);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0, date.getTime().toString());
|
||||
},
|
||||
0,
|
||||
date.getTime().toString(),
|
||||
);
|
||||
return pokerusStarters;
|
||||
}
|
||||
|
||||
export const allSpecies: PokemonSpecies[] = [];
|
||||
|
||||
// biome-ignore format: manually formatted
|
||||
export function initSpecies() {
|
||||
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),
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
|
|
@ -185,26 +185,38 @@ const seasonalSplashMessages: Season[] = [
|
|||
name: "Halloween",
|
||||
start: "09-15",
|
||||
end: "10-31",
|
||||
messages: [ "halloween.pumpkabooAbout", "halloween.mayContainSpiders", "halloween.spookyScarySkeledirge", "halloween.gourgeistUsedTrickOrTreat", "halloween.letsSnuggleForever" ],
|
||||
messages: [
|
||||
"halloween.pumpkabooAbout",
|
||||
"halloween.mayContainSpiders",
|
||||
"halloween.spookyScarySkeledirge",
|
||||
"halloween.gourgeistUsedTrickOrTreat",
|
||||
"halloween.letsSnuggleForever",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "XMAS",
|
||||
start: "12-01",
|
||||
end: "12-26",
|
||||
messages: [ "xmas.happyHolidays", "xmas.unaffilicatedWithDelibirdServices", "xmas.delibirdSeason", "xmas.diamondsFromTheSky", "xmas.holidayStylePikachuNotIncluded" ],
|
||||
messages: [
|
||||
"xmas.happyHolidays",
|
||||
"xmas.unaffilicatedWithDelibirdServices",
|
||||
"xmas.delibirdSeason",
|
||||
"xmas.diamondsFromTheSky",
|
||||
"xmas.holidayStylePikachuNotIncluded",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "New Year's",
|
||||
start: "01-01",
|
||||
end: "01-31",
|
||||
messages: [ "newYears.happyNewYear" ],
|
||||
messages: ["newYears.happyNewYear"],
|
||||
},
|
||||
];
|
||||
|
||||
//#endregion
|
||||
|
||||
export function getSplashMessages(): string[] {
|
||||
const splashMessages: string[] = [ ...commonSplashMessages ];
|
||||
const splashMessages: string[] = [...commonSplashMessages];
|
||||
console.log("use seasonal splash messages", USE_SEASONAL_SPLASH_MESSAGES);
|
||||
if (USE_SEASONAL_SPLASH_MESSAGES) {
|
||||
// add seasonal splash messages if the season is active
|
||||
|
@ -215,13 +227,13 @@ export function getSplashMessages(): string[] {
|
|||
|
||||
if (now >= startDate && now <= endDate) {
|
||||
console.log(`Adding ${messages.length} ${name} splash messages (weight: x${SEASONAL_WEIGHT_MULTIPLIER})`);
|
||||
messages.forEach((message) => {
|
||||
for (const message of messages) {
|
||||
const weightedMessage = Array(SEASONAL_WEIGHT_MULTIPLIER).fill(message);
|
||||
splashMessages.push(...weightedMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return splashMessages.map((message) => `splashMessages:${message}`);
|
||||
return splashMessages.map(message => `splashMessages:${message}`);
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@ import i18next from "i18next";
|
|||
export class Status {
|
||||
public effect: StatusEffect;
|
||||
/** Toxic damage is `1/16 max HP * toxicTurnCount` */
|
||||
public toxicTurnCount: number = 0;
|
||||
public toxicTurnCount = 0;
|
||||
public sleepTurnsRemaining?: number;
|
||||
|
||||
constructor(effect: StatusEffect, toxicTurnCount: number = 0, sleepTurnsRemaining?: number) {
|
||||
constructor(effect: StatusEffect, toxicTurnCount = 0, sleepTurnsRemaining?: number) {
|
||||
this.effect = effect;
|
||||
this.toxicTurnCount = toxicTurnCount;
|
||||
this.sleepTurnsRemaining = sleepTurnsRemaining;
|
||||
|
@ -23,7 +23,9 @@ export class Status {
|
|||
}
|
||||
|
||||
isPostTurn(): boolean {
|
||||
return this.effect === StatusEffect.POISON || this.effect === StatusEffect.TOXIC || this.effect === StatusEffect.BURN;
|
||||
return (
|
||||
this.effect === StatusEffect.POISON || this.effect === StatusEffect.TOXIC || this.effect === StatusEffect.BURN
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,17 +48,24 @@ function getStatusEffectMessageKey(statusEffect: StatusEffect | undefined): stri
|
|||
}
|
||||
}
|
||||
|
||||
export function getStatusEffectObtainText(statusEffect: StatusEffect | undefined, pokemonNameWithAffix: string, sourceText?: string): string {
|
||||
export function getStatusEffectObtainText(
|
||||
statusEffect: StatusEffect | undefined,
|
||||
pokemonNameWithAffix: string,
|
||||
sourceText?: string,
|
||||
): string {
|
||||
if (statusEffect === StatusEffect.NONE) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!sourceText) {
|
||||
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.obtain`as ParseKeys;
|
||||
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.obtain` as ParseKeys;
|
||||
return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix });
|
||||
}
|
||||
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.obtainSource`as ParseKeys;
|
||||
return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix, sourceText: sourceText });
|
||||
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.obtainSource` as ParseKeys;
|
||||
return i18next.t(i18nKey, {
|
||||
pokemonNameWithAffix: pokemonNameWithAffix,
|
||||
sourceText: sourceText,
|
||||
});
|
||||
}
|
||||
|
||||
export function getStatusEffectActivationText(statusEffect: StatusEffect, pokemonNameWithAffix: string): string {
|
||||
|
@ -107,17 +116,17 @@ export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect):
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a random non-volatile StatusEffect
|
||||
*/
|
||||
* Returns a random non-volatile StatusEffect
|
||||
*/
|
||||
export function generateRandomStatusEffect(): StatusEffect {
|
||||
return randIntRange(1, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random non-volatile StatusEffect between the two provided
|
||||
* @param statusEffectA The first StatusEffect
|
||||
* @param statusEffectA The second StatusEffect
|
||||
*/
|
||||
* Returns a random non-volatile StatusEffect between the two provided
|
||||
* @param statusEffectA The first StatusEffect
|
||||
* @param statusEffectA The second StatusEffect
|
||||
*/
|
||||
export function getRandomStatusEffect(statusEffectA: StatusEffect, statusEffectB: StatusEffect): StatusEffect {
|
||||
if (statusEffectA === StatusEffect.NONE || statusEffectA === StatusEffect.FAINT) {
|
||||
return statusEffectB;
|
||||
|
@ -130,10 +139,10 @@ export function getRandomStatusEffect(statusEffectA: StatusEffect, statusEffectB
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a random non-volatile StatusEffect between the two provided
|
||||
* @param statusA The first Status
|
||||
* @param statusB The second Status
|
||||
*/
|
||||
* Returns a random non-volatile StatusEffect between the two provided
|
||||
* @param statusA The first Status
|
||||
* @param statusB The second Status
|
||||
*/
|
||||
export function getRandomStatus(statusA: Status | null, statusB: Status | null): Status | null {
|
||||
if (!statusA || statusA.effect === StatusEffect.NONE || statusA.effect === StatusEffect.FAINT) {
|
||||
return statusB;
|
||||
|
@ -142,7 +151,6 @@ export function getRandomStatus(statusA: Status | null, statusB: Status | null):
|
|||
return statusA;
|
||||
}
|
||||
|
||||
|
||||
return randIntRange(0, 2) ? statusA : statusB;
|
||||
}
|
||||
|
||||
|
@ -150,14 +158,14 @@ export function getRandomStatus(statusA: Status | null, statusB: Status | null):
|
|||
* Gets all non volatile status effects
|
||||
* @returns A list containing all non volatile status effects
|
||||
*/
|
||||
export function getNonVolatileStatusEffects():Array<StatusEffect> {
|
||||
export function getNonVolatileStatusEffects(): Array<StatusEffect> {
|
||||
return [
|
||||
StatusEffect.POISON,
|
||||
StatusEffect.TOXIC,
|
||||
StatusEffect.PARALYSIS,
|
||||
StatusEffect.SLEEP,
|
||||
StatusEffect.FREEZE,
|
||||
StatusEffect.BURN
|
||||
StatusEffect.BURN,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ export enum TerrainType {
|
|||
MISTY,
|
||||
ELECTRIC,
|
||||
GRASSY,
|
||||
PSYCHIC
|
||||
PSYCHIC,
|
||||
}
|
||||
|
||||
export class Terrain {
|
||||
|
@ -57,7 +57,10 @@ export class Terrain {
|
|||
case TerrainType.PSYCHIC:
|
||||
if (!move.hasAttr(ProtectAttr)) {
|
||||
// Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain
|
||||
return move.getPriority(user) > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded());
|
||||
return (
|
||||
move.getPriority(user) > 0 &&
|
||||
user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,18 +83,17 @@ export function getTerrainName(terrainType: TerrainType): string {
|
|||
return "";
|
||||
}
|
||||
|
||||
|
||||
export function getTerrainColor(terrainType: TerrainType): [ number, number, number ] {
|
||||
export function getTerrainColor(terrainType: TerrainType): [number, number, number] {
|
||||
switch (terrainType) {
|
||||
case TerrainType.MISTY:
|
||||
return [ 232, 136, 200 ];
|
||||
return [232, 136, 200];
|
||||
case TerrainType.ELECTRIC:
|
||||
return [ 248, 248, 120 ];
|
||||
return [248, 248, 120];
|
||||
case TerrainType.GRASSY:
|
||||
return [ 120, 200, 80 ];
|
||||
return [120, 200, 80];
|
||||
case TerrainType.PSYCHIC:
|
||||
return [ 160, 64, 160 ];
|
||||
return [160, 64, 160];
|
||||
}
|
||||
|
||||
return [ 0, 0, 0 ];
|
||||
return [0, 0, 0];
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -271,7 +271,10 @@ export function getTypeDamageMultiplier(attackType: PokemonType, defType: Pokemo
|
|||
* Retrieve the color corresponding to a specific damage multiplier
|
||||
* @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") {
|
||||
switch (multiplier) {
|
||||
case 0:
|
||||
|
@ -291,7 +294,8 @@ export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, s
|
|||
case 8:
|
||||
return "#52C200";
|
||||
}
|
||||
} else if (side === "defense") {
|
||||
}
|
||||
if (side === "defense") {
|
||||
switch (multiplier) {
|
||||
case 0:
|
||||
return "#B1B100";
|
||||
|
@ -313,47 +317,47 @@ export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, s
|
|||
}
|
||||
}
|
||||
|
||||
export function getTypeRgb(type: PokemonType): [ number, number, number ] {
|
||||
export function getTypeRgb(type: PokemonType): [number, number, number] {
|
||||
switch (type) {
|
||||
case PokemonType.NORMAL:
|
||||
return [ 168, 168, 120 ];
|
||||
return [168, 168, 120];
|
||||
case PokemonType.FIGHTING:
|
||||
return [ 192, 48, 40 ];
|
||||
return [192, 48, 40];
|
||||
case PokemonType.FLYING:
|
||||
return [ 168, 144, 240 ];
|
||||
return [168, 144, 240];
|
||||
case PokemonType.POISON:
|
||||
return [ 160, 64, 160 ];
|
||||
return [160, 64, 160];
|
||||
case PokemonType.GROUND:
|
||||
return [ 224, 192, 104 ];
|
||||
return [224, 192, 104];
|
||||
case PokemonType.ROCK:
|
||||
return [ 184, 160, 56 ];
|
||||
return [184, 160, 56];
|
||||
case PokemonType.BUG:
|
||||
return [ 168, 184, 32 ];
|
||||
return [168, 184, 32];
|
||||
case PokemonType.GHOST:
|
||||
return [ 112, 88, 152 ];
|
||||
return [112, 88, 152];
|
||||
case PokemonType.STEEL:
|
||||
return [ 184, 184, 208 ];
|
||||
return [184, 184, 208];
|
||||
case PokemonType.FIRE:
|
||||
return [ 240, 128, 48 ];
|
||||
return [240, 128, 48];
|
||||
case PokemonType.WATER:
|
||||
return [ 104, 144, 240 ];
|
||||
return [104, 144, 240];
|
||||
case PokemonType.GRASS:
|
||||
return [ 120, 200, 80 ];
|
||||
return [120, 200, 80];
|
||||
case PokemonType.ELECTRIC:
|
||||
return [ 248, 208, 48 ];
|
||||
return [248, 208, 48];
|
||||
case PokemonType.PSYCHIC:
|
||||
return [ 248, 88, 136 ];
|
||||
return [248, 88, 136];
|
||||
case PokemonType.ICE:
|
||||
return [ 152, 216, 216 ];
|
||||
return [152, 216, 216];
|
||||
case PokemonType.DRAGON:
|
||||
return [ 112, 56, 248 ];
|
||||
return [112, 56, 248];
|
||||
case PokemonType.DARK:
|
||||
return [ 112, 88, 72 ];
|
||||
return [112, 88, 72];
|
||||
case PokemonType.FAIRY:
|
||||
return [ 232, 136, 200 ];
|
||||
return [232, 136, 200];
|
||||
case PokemonType.STELLAR:
|
||||
return [ 255, 255, 255 ];
|
||||
return [255, 255, 255];
|
||||
default:
|
||||
return [ 0, 0, 0 ];
|
||||
return [0, 0, 0];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,9 +106,13 @@ export class Weather {
|
|||
const field = globalScene.getField(true);
|
||||
|
||||
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) {
|
||||
suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] : null;
|
||||
suppressWeatherEffectAbAttr = pokemon.hasPassive()
|
||||
? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0]
|
||||
: null;
|
||||
}
|
||||
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) {
|
||||
return true;
|
||||
|
@ -172,9 +176,13 @@ export function getWeatherLapseMessage(weatherType: WeatherType): string | null
|
|||
export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string | null {
|
||||
switch (weatherType) {
|
||||
case WeatherType.SANDSTORM:
|
||||
return i18next.t("weather:sandstormDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
|
||||
return i18next.t("weather:sandstormDamageMessage", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
});
|
||||
case WeatherType.HAIL:
|
||||
return i18next.t("weather:hailDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
|
||||
return i18next.t("weather:hailDamageMessage", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -261,9 +269,14 @@ export function getTerrainClearMessage(terrainType: TerrainType): string | null
|
|||
|
||||
export function getTerrainBlockMessage(pokemon: Pokemon, terrainType: TerrainType): string {
|
||||
if (terrainType === TerrainType.MISTY) {
|
||||
return i18next.t("terrain:mistyBlockMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
|
||||
return i18next.t("terrain: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 {
|
||||
|
@ -276,9 +289,7 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
|
|||
const hasSun = arena.getTimeOfDay() < 2;
|
||||
switch (arena.biomeType) {
|
||||
case Biome.GRASS:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.NONE, weight: 7 }
|
||||
];
|
||||
weatherPool = [{ weatherType: WeatherType.NONE, weight: 7 }];
|
||||
if (hasSun) {
|
||||
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 3 });
|
||||
}
|
||||
|
@ -295,26 +306,26 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
|
|||
case Biome.FOREST:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.NONE, weight: 8 },
|
||||
{ weatherType: WeatherType.RAIN, weight: 5 }
|
||||
{ weatherType: WeatherType.RAIN, weight: 5 },
|
||||
];
|
||||
break;
|
||||
case Biome.SEA:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.NONE, weight: 3 },
|
||||
{ weatherType: WeatherType.RAIN, weight: 12 }
|
||||
{ weatherType: WeatherType.RAIN, weight: 12 },
|
||||
];
|
||||
break;
|
||||
case Biome.SWAMP:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.NONE, weight: 3 },
|
||||
{ weatherType: WeatherType.RAIN, weight: 4 },
|
||||
{ weatherType: WeatherType.FOG, weight: 1 }
|
||||
{ weatherType: WeatherType.FOG, weight: 1 },
|
||||
];
|
||||
break;
|
||||
case Biome.BEACH:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.NONE, weight: 8 },
|
||||
{ weatherType: WeatherType.RAIN, weight: 3 }
|
||||
{ weatherType: WeatherType.RAIN, weight: 3 },
|
||||
];
|
||||
if (hasSun) {
|
||||
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 });
|
||||
|
@ -324,27 +335,23 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
|
|||
weatherPool = [
|
||||
{ weatherType: WeatherType.NONE, weight: 10 },
|
||||
{ weatherType: WeatherType.RAIN, weight: 5 },
|
||||
{ weatherType: WeatherType.FOG, weight: 1 }
|
||||
{ weatherType: WeatherType.FOG, weight: 1 },
|
||||
];
|
||||
break;
|
||||
case Biome.SEABED:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.RAIN, weight: 1 }
|
||||
];
|
||||
weatherPool = [{ weatherType: WeatherType.RAIN, weight: 1 }];
|
||||
break;
|
||||
case Biome.BADLANDS:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.NONE, weight: 8 },
|
||||
{ weatherType: WeatherType.SANDSTORM, weight: 2 }
|
||||
{ weatherType: WeatherType.SANDSTORM, weight: 2 },
|
||||
];
|
||||
if (hasSun) {
|
||||
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 });
|
||||
}
|
||||
break;
|
||||
case Biome.DESERT:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.SANDSTORM, weight: 2 }
|
||||
];
|
||||
weatherPool = [{ weatherType: WeatherType.SANDSTORM, weight: 2 }];
|
||||
if (hasSun) {
|
||||
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 });
|
||||
}
|
||||
|
@ -353,37 +360,38 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
|
|||
weatherPool = [
|
||||
{ weatherType: WeatherType.NONE, weight: 3 },
|
||||
{ weatherType: WeatherType.SNOW, weight: 4 },
|
||||
{ weatherType: WeatherType.HAIL, weight: 1 }
|
||||
{ weatherType: WeatherType.HAIL, weight: 1 },
|
||||
];
|
||||
break;
|
||||
case Biome.MEADOW:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.NONE, weight: 2 }
|
||||
];
|
||||
weatherPool = [{ weatherType: WeatherType.NONE, weight: 2 }];
|
||||
if (hasSun) {
|
||||
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 });
|
||||
}
|
||||
case Biome.VOLCANO:
|
||||
weatherPool = [
|
||||
{ weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE, weight: 1 }
|
||||
{
|
||||
weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE,
|
||||
weight: 1,
|
||||
},
|
||||
];
|
||||
break;
|
||||
case Biome.GRAVEYARD:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.NONE, weight: 3 },
|
||||
{ weatherType: WeatherType.FOG, weight: 1 }
|
||||
{ weatherType: WeatherType.FOG, weight: 1 },
|
||||
];
|
||||
break;
|
||||
case Biome.JUNGLE:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.NONE, weight: 8 },
|
||||
{ weatherType: WeatherType.RAIN, weight: 2 }
|
||||
{ weatherType: WeatherType.RAIN, weight: 2 },
|
||||
];
|
||||
break;
|
||||
case Biome.SNOWY_FOREST:
|
||||
weatherPool = [
|
||||
{ weatherType: WeatherType.SNOW, weight: 7 },
|
||||
{ weatherType: WeatherType.HAIL, weight: 1 }
|
||||
{ weatherType: WeatherType.HAIL, weight: 1 },
|
||||
];
|
||||
break;
|
||||
case Biome.ISLAND:
|
||||
|
@ -403,7 +411,9 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
|
|||
|
||||
if (weatherPool.length > 1) {
|
||||
let totalWeight = 0;
|
||||
weatherPool.forEach(w => totalWeight += w.weight);
|
||||
for (const w of weatherPool) {
|
||||
totalWeight += w.weight;
|
||||
}
|
||||
|
||||
const rand = Utils.randSeedInt(totalWeight);
|
||||
let w = 0;
|
||||
|
@ -415,7 +425,5 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
|
|||
}
|
||||
}
|
||||
|
||||
return weatherPool.length
|
||||
? weatherPool[0].weatherType
|
||||
: WeatherType.NONE;
|
||||
return weatherPool.length ? weatherPool[0].weatherType : WeatherType.NONE;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@ export function getData() {
|
|||
if (!dataStr) {
|
||||
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() {
|
||||
|
|
|
@ -48,9 +48,11 @@ export type TempBattleStat = typeof TEMP_BATTLE_STATS[number];
|
|||
export function getStatStageChangeDescriptionKey(stages: number, isIncrease: boolean) {
|
||||
if (stages === 1) {
|
||||
return isIncrease ? "battle:statRose" : "battle:statFell";
|
||||
} else if (stages === 2) {
|
||||
}
|
||||
if (stages === 2) {
|
||||
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:statWontGoAnyHigher" : "battle:statWontGoAnyLower";
|
||||
|
|
|
@ -32,7 +32,7 @@ export class ArenaEvent extends Event {
|
|||
/**
|
||||
* Container class for {@linkcode ArenaEventType.WEATHER_CHANGED} events
|
||||
* @extends ArenaEvent
|
||||
*/
|
||||
*/
|
||||
export class WeatherChangedEvent extends ArenaEvent {
|
||||
/** The {@linkcode WeatherType} being overridden */
|
||||
public oldWeatherType: WeatherType;
|
||||
|
@ -48,7 +48,7 @@ export class WeatherChangedEvent extends ArenaEvent {
|
|||
/**
|
||||
* Container class for {@linkcode ArenaEventType.TERRAIN_CHANGED} events
|
||||
* @extends ArenaEvent
|
||||
*/
|
||||
*/
|
||||
export class TerrainChangedEvent extends ArenaEvent {
|
||||
/** The {@linkcode TerrainType} being overridden */
|
||||
public oldTerrainType: TerrainType;
|
||||
|
@ -65,7 +65,7 @@ export class TerrainChangedEvent extends ArenaEvent {
|
|||
/**
|
||||
* Container class for {@linkcode ArenaEventType.TAG_ADDED} events
|
||||
* @extends ArenaEvent
|
||||
*/
|
||||
*/
|
||||
export class TagAddedEvent extends ArenaEvent {
|
||||
/** The {@linkcode ArenaTagType} being added */
|
||||
public arenaTagType: ArenaTagType;
|
||||
|
@ -76,7 +76,13 @@ export class TagAddedEvent extends ArenaEvent {
|
|||
/** The maximum amount of layers of the arena trap. */
|
||||
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);
|
||||
|
||||
this.arenaTagType = arenaTagType;
|
||||
|
@ -88,7 +94,7 @@ export class TagAddedEvent extends ArenaEvent {
|
|||
/**
|
||||
* Container class for {@linkcode ArenaEventType.TAG_REMOVED} events
|
||||
* @extends ArenaEvent
|
||||
*/
|
||||
*/
|
||||
export class TagRemovedEvent extends ArenaEvent {
|
||||
/** The {@linkcode ArenaTagType} being removed */
|
||||
public arenaTagType: ArenaTagType;
|
||||
|
|
|
@ -34,7 +34,7 @@ export enum BattleSceneEventType {
|
|||
* Triggers after a turn ends in battle
|
||||
* @see {@linkcode TurnEndEvent}
|
||||
*/
|
||||
TURN_END = "onTurnEnd",
|
||||
TURN_END = "onTurnEnd",
|
||||
|
||||
/**
|
||||
* Triggers when a new {@linkcode Arena} is created during initialization
|
||||
|
@ -46,7 +46,7 @@ export enum BattleSceneEventType {
|
|||
/**
|
||||
* Container class for {@linkcode BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED} events
|
||||
* @extends Event
|
||||
*/
|
||||
*/
|
||||
export class CandyUpgradeNotificationChangedEvent extends Event {
|
||||
/** The new value the setting was changed to */
|
||||
public newValue: number;
|
||||
|
@ -60,7 +60,7 @@ export class CandyUpgradeNotificationChangedEvent extends Event {
|
|||
/**
|
||||
* Container class for {@linkcode BattleSceneEventType.MOVE_USED} events
|
||||
* @extends Event
|
||||
*/
|
||||
*/
|
||||
export class MoveUsedEvent extends Event {
|
||||
/** The ID of the {@linkcode Pokemon} that used the {@linkcode Move} */
|
||||
public pokemonId: number;
|
||||
|
@ -79,7 +79,7 @@ export class MoveUsedEvent extends Event {
|
|||
/**
|
||||
* Container class for {@linkcode BattleSceneEventType.BERRY_USED} events
|
||||
* @extends Event
|
||||
*/
|
||||
*/
|
||||
export class BerryUsedEvent extends Event {
|
||||
/** The {@linkcode BerryModifier} being used */
|
||||
public berryModifier: BerryModifier;
|
||||
|
@ -93,7 +93,7 @@ export class BerryUsedEvent extends Event {
|
|||
/**
|
||||
* Container class for {@linkcode BattleSceneEventType.ENCOUNTER_PHASE} events
|
||||
* @extends Event
|
||||
*/
|
||||
*/
|
||||
export class EncounterPhaseEvent extends Event {
|
||||
constructor() {
|
||||
super(BattleSceneEventType.ENCOUNTER_PHASE);
|
||||
|
@ -102,7 +102,7 @@ export class EncounterPhaseEvent extends Event {
|
|||
/**
|
||||
* Container class for {@linkcode BattleSceneEventType.TURN_INIT} events
|
||||
* @extends Event
|
||||
*/
|
||||
*/
|
||||
export class TurnInitEvent extends Event {
|
||||
constructor() {
|
||||
super(BattleSceneEventType.TURN_INIT);
|
||||
|
@ -111,7 +111,7 @@ export class TurnInitEvent extends Event {
|
|||
/**
|
||||
* Container class for {@linkcode BattleSceneEventType.TURN_END} events
|
||||
* @extends Event
|
||||
*/
|
||||
*/
|
||||
export class TurnEndEvent extends Event {
|
||||
/** The amount of turns in the current battle */
|
||||
public turnCount: number;
|
||||
|
@ -124,7 +124,7 @@ export class TurnEndEvent extends Event {
|
|||
/**
|
||||
* Container class for {@linkcode BattleSceneEventType.NEW_ARENA} events
|
||||
* @extends Event
|
||||
*/
|
||||
*/
|
||||
export class NewArenaEvent extends Event {
|
||||
constructor() {
|
||||
super(BattleSceneEventType.NEW_ARENA);
|
||||
|
|
|
@ -3,13 +3,13 @@ export enum EggEventType {
|
|||
* Triggers when egg count is changed.
|
||||
* @see {@linkcode MoveUsedEvent}
|
||||
*/
|
||||
EGG_COUNT_CHANGED = "onEggCountChanged"
|
||||
EGG_COUNT_CHANGED = "onEggCountChanged",
|
||||
}
|
||||
|
||||
/**
|
||||
* Container class for {@linkcode EggEventType.EGG_COUNT_CHANGED} events
|
||||
* @extends Event
|
||||
*/
|
||||
*/
|
||||
export class EggCountChangedEvent extends Event {
|
||||
/** The updated egg count. */
|
||||
public eggCount: number;
|
||||
|
|
|
@ -24,13 +24,17 @@ export function addPokeballOpenParticles(x: number, y: number, pokeballType: Pok
|
|||
}
|
||||
|
||||
function doDefaultPbOpenParticles(x: number, y: number, radius: number) {
|
||||
const pbOpenParticlesFrameNames = globalScene.anims.generateFrameNames("pb_particles", { start: 0, end: 3, suffix: ".png" });
|
||||
if (!(globalScene.anims.exists("pb_open_particle"))) {
|
||||
const pbOpenParticlesFrameNames = globalScene.anims.generateFrameNames("pb_particles", {
|
||||
start: 0,
|
||||
end: 3,
|
||||
suffix: ".png",
|
||||
});
|
||||
if (!globalScene.anims.exists("pb_open_particle")) {
|
||||
globalScene.anims.create({
|
||||
key: "pb_open_particle",
|
||||
frames: pbOpenParticlesFrameNames,
|
||||
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");
|
||||
globalScene.field.add(particle);
|
||||
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({
|
||||
targets: particle,
|
||||
x: x + xCoord,
|
||||
y: y + yCoord,
|
||||
duration: 575
|
||||
duration: 575,
|
||||
});
|
||||
particle.play({
|
||||
key: "pb_open_particle",
|
||||
startFrame: (index + 3) % 4,
|
||||
frameRate: Math.floor(16 * globalScene.gameSpeed)
|
||||
frameRate: Math.floor(16 * globalScene.gameSpeed),
|
||||
});
|
||||
globalScene.tweens.add({
|
||||
targets: particle,
|
||||
|
@ -56,7 +60,7 @@ function doDefaultPbOpenParticles(x: number, y: number, radius: number) {
|
|||
duration: 75,
|
||||
alpha: 0,
|
||||
ease: "Sine.easeIn",
|
||||
onComplete: () => particle.destroy()
|
||||
onComplete: () => particle.destroy(),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -64,7 +68,7 @@ function doDefaultPbOpenParticles(x: number, y: number, radius: number) {
|
|||
globalScene.time.addEvent({
|
||||
delay: 20,
|
||||
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) {
|
||||
particle.destroy();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -105,12 +109,20 @@ function doMbOpenParticles(x: number, y: number) {
|
|||
for (const particle of particles) {
|
||||
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;
|
||||
|
||||
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.y = y + cos(trigIndex, f * ySpeed);
|
||||
trigIndex = (trigIndex + angle);
|
||||
trigIndex = trigIndex + angle;
|
||||
f++;
|
||||
};
|
||||
|
||||
|
@ -131,7 +143,7 @@ function doFanOutParticle(trigIndex: number, x: number, y: number, xSpeed: numbe
|
|||
duration: getFrameMs(1),
|
||||
onRepeat: () => {
|
||||
updateParticle();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return particle;
|
||||
|
@ -155,16 +167,16 @@ export function addPokeballCaptureStars(pokeball: Phaser.GameObjects.Sprite): vo
|
|||
y: pokeball.y,
|
||||
alpha: 0,
|
||||
ease: "Sine.easeIn",
|
||||
duration: 250
|
||||
duration: 250,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const dist = randGauss(25);
|
||||
globalScene.tweens.add({
|
||||
targets: particle,
|
||||
x: pokeball.x + dist,
|
||||
duration: 500
|
||||
duration: 500,
|
||||
});
|
||||
|
||||
globalScene.tweens.add({
|
||||
|
@ -172,7 +184,7 @@ export function addPokeballCaptureStars(pokeball: Phaser.GameObjects.Sprite): vo
|
|||
alpha: 0,
|
||||
delay: 425,
|
||||
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
|
||||
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({
|
||||
key: `sparkle${keySuffix}`,
|
||||
frames: frameNames,
|
||||
|
|
|
@ -5,7 +5,14 @@ import type { Constructor } from "#app/utils";
|
|||
import * as Utils from "#app/utils";
|
||||
import type PokemonSpecies 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 type { PokemonType } from "#enums/pokemon-type";
|
||||
import type Move from "#app/data/moves/move";
|
||||
|
@ -19,7 +26,7 @@ import {
|
|||
applyPostWeatherChangeAbAttrs,
|
||||
PostTerrainChangeAbAttr,
|
||||
PostWeatherChangeAbAttr,
|
||||
TerrainEventTypeChangeAbAttr
|
||||
TerrainEventTypeChangeAbAttr,
|
||||
} from "#app/data/ability";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import Overrides from "#app/overrides";
|
||||
|
@ -58,7 +65,7 @@ export class Arena {
|
|||
|
||||
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.tags = [];
|
||||
this.bgm = bgm;
|
||||
|
@ -88,19 +95,29 @@ export class Arena {
|
|||
if (timeOfDay !== this.lastTimeOfDay) {
|
||||
this.pokemonPool = {};
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (overrideSpecies) {
|
||||
return overrideSpecies;
|
||||
}
|
||||
const isBossSpecies = !!globalScene.getEncounterBossSegments(waveIndex, level) && !!this.pokemonPool[BiomePoolTier.BOSS].length
|
||||
&& (this.biomeType !== Biome.END || globalScene.gameMode.isClassic || globalScene.gameMode.isWaveFinal(waveIndex));
|
||||
const isBossSpecies =
|
||||
!!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;
|
||||
// luck influences encounter rarity
|
||||
let luckModifier = 0;
|
||||
|
@ -109,8 +126,22 @@ export class Arena {
|
|||
}
|
||||
const tierValue = Utils.randSeedInt(randVal - luckModifier);
|
||||
let tier = !isBossSpecies
|
||||
? tierValue >= 156 ? 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;
|
||||
? tierValue >= 156
|
||||
? 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]);
|
||||
while (!this.pokemonPool[tier].length) {
|
||||
console.log(`Downgraded rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`);
|
||||
|
@ -129,7 +160,7 @@ export class Arena {
|
|||
} else {
|
||||
const levelThresholds = Object.keys(entry);
|
||||
for (let l = levelThresholds.length - 1; l >= 0; l--) {
|
||||
const levelThreshold = parseInt(levelThresholds[l]);
|
||||
const levelThreshold = Number.parseInt(levelThresholds[l]);
|
||||
if (level >= levelThreshold) {
|
||||
const speciesIds = entry[levelThreshold];
|
||||
if (speciesIds.length > 1) {
|
||||
|
@ -146,13 +177,13 @@ export class Arena {
|
|||
|
||||
if (ret.subLegendary || ret.legendary || ret.mythical) {
|
||||
switch (true) {
|
||||
case (ret.baseTotal >= 720):
|
||||
case ret.baseTotal >= 720:
|
||||
regen = level < 90;
|
||||
break;
|
||||
case (ret.baseTotal >= 670):
|
||||
case ret.baseTotal >= 670:
|
||||
regen = level < 70;
|
||||
break;
|
||||
case (ret.baseTotal >= 580):
|
||||
case ret.baseTotal >= 580:
|
||||
regen = level < 50;
|
||||
break;
|
||||
default:
|
||||
|
@ -175,14 +206,29 @@ export class Arena {
|
|||
return ret;
|
||||
}
|
||||
|
||||
randomTrainerType(waveIndex: number, isBoss: boolean = false): TrainerType {
|
||||
const isTrainerBoss = !!this.trainerPool[BiomePoolTier.BOSS].length
|
||||
&& (globalScene.gameMode.isTrainerBoss(waveIndex, this.biomeType, globalScene.offsetGym) || isBoss);
|
||||
randomTrainerType(waveIndex: number, isBoss = false): TrainerType {
|
||||
const isTrainerBoss =
|
||||
!!this.trainerPool[BiomePoolTier.BOSS].length &&
|
||||
(globalScene.gameMode.isTrainerBoss(waveIndex, this.biomeType, globalScene.offsetGym) || isBoss);
|
||||
console.log(isBoss, this.trainerPool);
|
||||
const tierValue = Utils.randSeedInt(!isTrainerBoss ? 512 : 64);
|
||||
let tier = !isTrainerBoss
|
||||
? tierValue >= 156 ? 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;
|
||||
? tierValue >= 156
|
||||
? 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]);
|
||||
while (tier && !this.trainerPool[tier].length) {
|
||||
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;
|
||||
|
||||
if (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));
|
||||
if (
|
||||
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)!);
|
||||
return false;
|
||||
}
|
||||
|
||||
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) {
|
||||
globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1), true));
|
||||
|
@ -290,10 +343,15 @@ export class Arena {
|
|||
globalScene.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct?
|
||||
}
|
||||
|
||||
globalScene.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);
|
||||
});
|
||||
globalScene
|
||||
.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);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -302,9 +360,9 @@ export class Arena {
|
|||
* Function to trigger all weather based form changes
|
||||
*/
|
||||
triggerWeatherBasedFormChanges(): void {
|
||||
globalScene.getField(true).forEach( p => {
|
||||
const isCastformWithForecast = (p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM);
|
||||
const isCherrimWithFlowerGift = (p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM);
|
||||
globalScene.getField(true).forEach(p => {
|
||||
const isCastformWithForecast = p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM;
|
||||
const isCherrimWithFlowerGift = p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM;
|
||||
|
||||
if (isCastformWithForecast || isCherrimWithFlowerGift) {
|
||||
new ShowAbilityPhase(p.getBattlerIndex());
|
||||
|
@ -317,9 +375,11 @@ export class Arena {
|
|||
* Function to trigger all weather based form changes back into their normal forms
|
||||
*/
|
||||
triggerWeatherBasedFormChangesToNormal(): void {
|
||||
globalScene.getField(true).forEach( p => {
|
||||
const isCastformWithForecast = (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);
|
||||
globalScene.getField(true).forEach(p => {
|
||||
const isCastformWithForecast =
|
||||
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) {
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -336,7 +396,9 @@ export class Arena {
|
|||
const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE;
|
||||
|
||||
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 (!ignoreAnim) {
|
||||
|
@ -347,11 +409,16 @@ export class Arena {
|
|||
globalScene.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct?
|
||||
}
|
||||
|
||||
globalScene.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);
|
||||
applyAbAttrs(TerrainEventTypeChangeAbAttr, pokemon, null, false);
|
||||
});
|
||||
globalScene
|
||||
.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);
|
||||
applyAbAttrs(TerrainEventTypeChangeAbAttr, pokemon, null, false);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -478,16 +545,13 @@ export class Arena {
|
|||
overrideTint(): [number, number, number] {
|
||||
switch (Overrides.ARENA_TINT_OVERRIDE) {
|
||||
case TimeOfDay.DUSK:
|
||||
return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [number, number, number];
|
||||
break;
|
||||
case (TimeOfDay.NIGHT):
|
||||
return [ 64, 64, 64 ];
|
||||
break;
|
||||
return [98, 48, 73].map(c => Math.round((c + 128) / 2)) as [number, number, number];
|
||||
case TimeOfDay.NIGHT:
|
||||
return [64, 64, 64];
|
||||
case TimeOfDay.DAWN:
|
||||
case TimeOfDay.DAY:
|
||||
default:
|
||||
return [ 128, 128, 128 ];
|
||||
break;
|
||||
return [128, 128, 128];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,9 +561,9 @@ export class Arena {
|
|||
}
|
||||
switch (this.biomeType) {
|
||||
case Biome.ABYSS:
|
||||
return [ 64, 64, 64 ];
|
||||
return [64, 64, 64];
|
||||
default:
|
||||
return [ 128, 128, 128 ];
|
||||
return [128, 128, 128];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -508,12 +572,12 @@ export class Arena {
|
|||
return this.overrideTint();
|
||||
}
|
||||
if (!this.isOutside()) {
|
||||
return [ 0, 0, 0 ];
|
||||
return [0, 0, 0];
|
||||
}
|
||||
|
||||
switch (this.biomeType) {
|
||||
default:
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -529,12 +593,12 @@ export class Arena {
|
|||
}
|
||||
|
||||
if (!this.isOutside()) {
|
||||
return [ 64, 64, 64 ];
|
||||
return [64, 64, 64];
|
||||
}
|
||||
|
||||
switch (this.biomeType) {
|
||||
default:
|
||||
return [ 48, 48, 98 ];
|
||||
return [48, 48, 98];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -550,10 +614,16 @@ export class Arena {
|
|||
* @param simulated if `true`, this applies arena tags without changing game state
|
||||
* @param args array of parameters that the called upon tags may need
|
||||
*/
|
||||
applyTagsForSide(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 instanceof tagType);
|
||||
applyTagsForSide(
|
||||
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 instanceof tagType);
|
||||
if (side !== ArenaTagSide.BOTH) {
|
||||
tags = tags.filter(t => t.side === side);
|
||||
}
|
||||
|
@ -582,7 +652,15 @@ export class Arena {
|
|||
* @param targetIndex The {@linkcode BattlerIndex} of the target pokemon
|
||||
* @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);
|
||||
if (existingTag) {
|
||||
existingTag.onOverlap(this);
|
||||
|
@ -603,7 +681,9 @@ export class Arena {
|
|||
|
||||
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;
|
||||
|
@ -618,7 +698,7 @@ export class Arena {
|
|||
return this.getTagOnSide(tagType, ArenaTagSide.BOTH);
|
||||
}
|
||||
|
||||
hasTag(tagType: ArenaTagType) : boolean {
|
||||
hasTag(tagType: ArenaTagType): boolean {
|
||||
return !!this.getTag(tagType);
|
||||
}
|
||||
|
||||
|
@ -631,9 +711,13 @@ export class Arena {
|
|||
* @returns either the {@linkcode ArenaTag}, or `undefined` if it isn't there
|
||||
*/
|
||||
getTagOnSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide): ArenaTag | undefined {
|
||||
return typeof(tagType) === "string"
|
||||
? this.tags.find(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));
|
||||
return typeof tagType === "string"
|
||||
? this.tags.find(
|
||||
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,16 +736,20 @@ export class Arena {
|
|||
* @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[] {
|
||||
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 {
|
||||
this.tags.filter(t => !(t.lapse(this))).forEach(t => {
|
||||
t.onRemove(this);
|
||||
this.tags.splice(this.tags.indexOf(t), 1);
|
||||
this.tags
|
||||
.filter(t => !t.lapse(this))
|
||||
.forEach(t => {
|
||||
t.onRemove(this);
|
||||
this.tags.splice(this.tags.indexOf(t), 1);
|
||||
|
||||
this.eventTarget.dispatchEvent(new TagRemovedEvent(t.tagType, t.side, t.turnCount));
|
||||
});
|
||||
this.eventTarget.dispatchEvent(new TagRemovedEvent(t.tagType, t.side, t.turnCount));
|
||||
});
|
||||
}
|
||||
|
||||
removeTag(tagType: ArenaTagType): boolean {
|
||||
|
@ -676,7 +764,7 @@ export class Arena {
|
|||
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);
|
||||
if (tag) {
|
||||
tag.onRemove(this, quiet);
|
||||
|
@ -687,11 +775,12 @@ export class Arena {
|
|||
return !!tag;
|
||||
}
|
||||
|
||||
|
||||
removeAllTags(): void {
|
||||
while (this.tags.length) {
|
||||
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);
|
||||
}
|
||||
|
@ -726,7 +815,7 @@ export class Arena {
|
|||
case Biome.TALL_GRASS:
|
||||
return 9.608;
|
||||
case Biome.METROPOLIS:
|
||||
return 141.470;
|
||||
return 141.47;
|
||||
case Biome.FOREST:
|
||||
return 0.341;
|
||||
case Biome.SEA:
|
||||
|
@ -738,17 +827,17 @@ export class Arena {
|
|||
case Biome.LAKE:
|
||||
return 7.215;
|
||||
case Biome.SEABED:
|
||||
return 2.600;
|
||||
return 2.6;
|
||||
case Biome.MOUNTAIN:
|
||||
return 4.018;
|
||||
case Biome.BADLANDS:
|
||||
return 17.790;
|
||||
return 17.79;
|
||||
case Biome.CAVE:
|
||||
return 14.240;
|
||||
return 14.24;
|
||||
case Biome.DESERT:
|
||||
return 1.143;
|
||||
case Biome.ICE_CAVE:
|
||||
return 0.000;
|
||||
return 0.0;
|
||||
case Biome.MEADOW:
|
||||
return 3.891;
|
||||
case Biome.POWER_PLANT:
|
||||
|
@ -762,17 +851,17 @@ export class Arena {
|
|||
case Biome.FACTORY:
|
||||
return 4.985;
|
||||
case Biome.RUINS:
|
||||
return 0.000;
|
||||
return 0.0;
|
||||
case Biome.WASTELAND:
|
||||
return 6.336;
|
||||
case Biome.ABYSS:
|
||||
return 5.130;
|
||||
return 5.13;
|
||||
case Biome.SPACE:
|
||||
return 20.036;
|
||||
case Biome.CONSTRUCTION_SITE:
|
||||
return 1.222;
|
||||
case Biome.JUNGLE:
|
||||
return 0.000;
|
||||
return 0.0;
|
||||
case Biome.FAIRY_CAVE:
|
||||
return 4.542;
|
||||
case Biome.TEMPLE:
|
||||
|
@ -782,7 +871,7 @@ export class Arena {
|
|||
case Biome.LABORATORY:
|
||||
return 114.862;
|
||||
case Biome.SLUM:
|
||||
return 0.000;
|
||||
return 0.0;
|
||||
case Biome.SNOWY_FOREST:
|
||||
return 3.047;
|
||||
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.setOrigin(0, 0);
|
||||
|
||||
this.props = !player ?
|
||||
new Array(3).fill(null).map(() => {
|
||||
const ret = globalScene.addFieldSprite(0, 0, "plains_b", undefined, 1);
|
||||
ret.setOrigin(0, 0);
|
||||
ret.setVisible(false);
|
||||
return ret;
|
||||
}) : [];
|
||||
this.props = !player
|
||||
? new Array(3).fill(null).map(() => {
|
||||
const ret = globalScene.addFieldSprite(0, 0, "plains_b", undefined, 1);
|
||||
ret.setOrigin(0, 0);
|
||||
ret.setVisible(false);
|
||||
return ret;
|
||||
})
|
||||
: [];
|
||||
}
|
||||
|
||||
setBiome(biome: Biome, propValue?: number): void {
|
||||
|
@ -868,13 +958,18 @@ export class ArenaBase extends Phaser.GameObjects.Container {
|
|||
this.base.setTexture(baseKey);
|
||||
|
||||
if (this.base.texture.frameTotal > 1) {
|
||||
const baseFrameNames = globalScene.anims.generateFrameNames(baseKey, { zeroPad: 4, suffix: ".png", start: 1, end: this.base.texture.frameTotal - 1 });
|
||||
if (!(globalScene.anims.exists(baseKey))) {
|
||||
const baseFrameNames = globalScene.anims.generateFrameNames(baseKey, {
|
||||
zeroPad: 4,
|
||||
suffix: ".png",
|
||||
start: 1,
|
||||
end: this.base.texture.frameTotal - 1,
|
||||
});
|
||||
if (!globalScene.anims.exists(baseKey)) {
|
||||
globalScene.anims.create({
|
||||
key: baseKey,
|
||||
frames: baseFrameNames,
|
||||
frameRate: 12,
|
||||
repeat: -1
|
||||
repeat: -1,
|
||||
});
|
||||
}
|
||||
this.base.play(baseKey);
|
||||
|
@ -886,33 +981,40 @@ export class ArenaBase extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
if (!this.player) {
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
this.propValue = propValue === undefined
|
||||
? hasProps ? Utils.randSeedInt(8) : 0
|
||||
: propValue;
|
||||
this.props.forEach((prop, p) => {
|
||||
const propKey = `${biomeKey}_b${hasProps ? `_${p + 1}` : ""}`;
|
||||
prop.setTexture(propKey);
|
||||
globalScene.executeWithSeedOffset(
|
||||
() => {
|
||||
this.propValue = propValue === undefined ? (hasProps ? Utils.randSeedInt(8) : 0) : propValue;
|
||||
this.props.forEach((prop, p) => {
|
||||
const propKey = `${biomeKey}_b${hasProps ? `_${p + 1}` : ""}`;
|
||||
prop.setTexture(propKey);
|
||||
|
||||
if (hasProps && prop.texture.frameTotal > 1) {
|
||||
const propFrameNames = globalScene.anims.generateFrameNames(propKey, { zeroPad: 4, suffix: ".png", start: 1, end: prop.texture.frameTotal - 1 });
|
||||
if (!(globalScene.anims.exists(propKey))) {
|
||||
globalScene.anims.create({
|
||||
key: propKey,
|
||||
frames: propFrameNames,
|
||||
frameRate: 12,
|
||||
repeat: -1
|
||||
if (hasProps && prop.texture.frameTotal > 1) {
|
||||
const propFrameNames = globalScene.anims.generateFrameNames(propKey, {
|
||||
zeroPad: 4,
|
||||
suffix: ".png",
|
||||
start: 1,
|
||||
end: prop.texture.frameTotal - 1,
|
||||
});
|
||||
if (!globalScene.anims.exists(propKey)) {
|
||||
globalScene.anims.create({
|
||||
key: propKey,
|
||||
frames: propFrameNames,
|
||||
frameRate: 12,
|
||||
repeat: -1,
|
||||
});
|
||||
}
|
||||
prop.play(propKey);
|
||||
} else {
|
||||
prop.stop();
|
||||
}
|
||||
prop.play(propKey);
|
||||
} else {
|
||||
prop.stop();
|
||||
}
|
||||
|
||||
prop.setVisible(hasProps && !!(this.propValue & (1 << p)));
|
||||
this.add(prop);
|
||||
});
|
||||
}, globalScene.currentBattle?.waveIndex || 0, globalScene.waveSeed);
|
||||
prop.setVisible(hasProps && !!(this.propValue & (1 << p)));
|
||||
this.add(prop);
|
||||
});
|
||||
},
|
||||
globalScene.currentBattle?.waveIndex || 0,
|
||||
globalScene.waveSeed,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import * as Utils from "../utils";
|
|||
import type { BattlerIndex } from "../battle";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
type TextAndShadowArr = [ string | null, string | null ];
|
||||
type TextAndShadowArr = [string | null, string | null];
|
||||
|
||||
export default class DamageNumberHandler {
|
||||
private damageNumbers: Map<BattlerIndex, Phaser.GameObjects.Text[]>;
|
||||
|
@ -15,35 +15,45 @@ export default class DamageNumberHandler {
|
|||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
const battlerIndex = target.getBattlerIndex();
|
||||
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.setOrigin(0.5, 1);
|
||||
damageNumber.setScale(baseScale);
|
||||
|
||||
let [ textColor, shadowColor ] : TextAndShadowArr = [ null, null ];
|
||||
let [textColor, shadowColor]: TextAndShadowArr = [null, null];
|
||||
|
||||
switch (result) {
|
||||
case HitResult.SUPER_EFFECTIVE:
|
||||
[ textColor, shadowColor ] = [ "#f8d030", "#b8a038" ];
|
||||
[textColor, shadowColor] = ["#f8d030", "#b8a038"];
|
||||
break;
|
||||
case HitResult.NOT_VERY_EFFECTIVE:
|
||||
[ textColor, shadowColor ] = [ "#f08030", "#c03028" ];
|
||||
[textColor, shadowColor] = ["#f08030", "#c03028"];
|
||||
break;
|
||||
case HitResult.ONE_HIT_KO:
|
||||
[ textColor, shadowColor ] = [ "#a040a0", "#483850" ];
|
||||
[textColor, shadowColor] = ["#a040a0", "#483850"];
|
||||
break;
|
||||
case HitResult.HEAL:
|
||||
[ textColor, shadowColor ] = [ "#78c850", "#588040" ];
|
||||
[textColor, shadowColor] = ["#78c850", "#588040"];
|
||||
break;
|
||||
default:
|
||||
[ textColor, shadowColor ] = [ "#ffffff", "#636363" ];
|
||||
[textColor, shadowColor] = ["#ffffff", "#636363"];
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -77,7 +87,7 @@ export default class DamageNumberHandler {
|
|||
targets: damageNumber,
|
||||
duration: Utils.fixedInt(750),
|
||||
alpha: 1,
|
||||
y: "-=32"
|
||||
y: "-=32",
|
||||
});
|
||||
globalScene.tweens.add({
|
||||
delay: 375,
|
||||
|
@ -88,7 +98,7 @@ export default class DamageNumberHandler {
|
|||
onComplete: () => {
|
||||
this.damageNumbers.get(battlerIndex)!.splice(this.damageNumbers.get(battlerIndex)!.indexOf(damageNumber), 1);
|
||||
damageNumber.destroy(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -104,7 +114,7 @@ export default class DamageNumberHandler {
|
|||
scaleX: 0.75 * baseScale,
|
||||
scaleY: 1.25 * baseScale,
|
||||
y: "-=16",
|
||||
ease: "Cubic.easeOut"
|
||||
ease: "Cubic.easeOut",
|
||||
},
|
||||
{
|
||||
duration: Utils.fixedInt(175),
|
||||
|
@ -112,69 +122,71 @@ export default class DamageNumberHandler {
|
|||
scaleX: 0.875 * baseScale,
|
||||
scaleY: 1.125 * baseScale,
|
||||
y: "+=16",
|
||||
ease: "Cubic.easeIn"
|
||||
ease: "Cubic.easeIn",
|
||||
},
|
||||
{
|
||||
duration: Utils.fixedInt(100),
|
||||
scaleX: 1.25 * baseScale,
|
||||
scaleY: 0.75 * baseScale,
|
||||
ease: "Cubic.easeOut"
|
||||
ease: "Cubic.easeOut",
|
||||
},
|
||||
{
|
||||
duration: Utils.fixedInt(175),
|
||||
scaleX: 0.875 * baseScale,
|
||||
scaleY: 1.125 * baseScale,
|
||||
y: "-=8",
|
||||
ease: "Cubic.easeOut"
|
||||
ease: "Cubic.easeOut",
|
||||
},
|
||||
{
|
||||
duration: Utils.fixedInt(50),
|
||||
scaleX: 0.925 * baseScale,
|
||||
scaleY: 1.075 * baseScale,
|
||||
y: "+=8",
|
||||
ease: "Cubic.easeIn"
|
||||
ease: "Cubic.easeIn",
|
||||
},
|
||||
{
|
||||
duration: Utils.fixedInt(100),
|
||||
scaleX: 1.125 * baseScale,
|
||||
scaleY: 0.875 * baseScale,
|
||||
ease: "Cubic.easeOut"
|
||||
ease: "Cubic.easeOut",
|
||||
},
|
||||
{
|
||||
duration: Utils.fixedInt(175),
|
||||
scaleX: 0.925 * baseScale,
|
||||
scaleY: 1.075 * baseScale,
|
||||
y: "-=4",
|
||||
ease: "Cubic.easeOut"
|
||||
ease: "Cubic.easeOut",
|
||||
},
|
||||
{
|
||||
duration: Utils.fixedInt(50),
|
||||
scaleX: 0.975 * baseScale,
|
||||
scaleY: 1.025 * baseScale,
|
||||
y: "+=4",
|
||||
ease: "Cubic.easeIn"
|
||||
ease: "Cubic.easeIn",
|
||||
},
|
||||
{
|
||||
duration: Utils.fixedInt(100),
|
||||
scaleX: 1.075 * baseScale,
|
||||
scaleY: 0.925 * baseScale,
|
||||
ease: "Cubic.easeOut"
|
||||
ease: "Cubic.easeOut",
|
||||
},
|
||||
{
|
||||
duration: Utils.fixedInt(25),
|
||||
scaleX: baseScale,
|
||||
scaleY: baseScale,
|
||||
ease: "Cubic.easeOut"
|
||||
ease: "Cubic.easeOut",
|
||||
},
|
||||
{
|
||||
delay: Utils.fixedInt(500),
|
||||
alpha: 0,
|
||||
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);
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ export class MysteryEncounterSpriteConfig {
|
|||
/** The sprite key (which is the image file name). e.g. "ace_trainer_f" */
|
||||
spriteKey: string;
|
||||
/** 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 */
|
||||
species?: Species;
|
||||
/** Enable shadow. Defaults to `false` */
|
||||
|
@ -80,7 +80,10 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
public encounter: MysteryEncounter;
|
||||
public spriteConfigs: MysteryEncounterSpriteConfig[];
|
||||
public enterFromRight: boolean;
|
||||
private shinySparkleSprites: { sprite: Phaser.GameObjects.Sprite, variant: Variant }[];
|
||||
private shinySparkleSprites: {
|
||||
sprite: Phaser.GameObjects.Sprite;
|
||||
variant: Variant;
|
||||
}[];
|
||||
|
||||
constructor(encounter: MysteryEncounter) {
|
||||
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
|
||||
this.spriteConfigs = encounter.spriteConfigs.map(config => {
|
||||
const result = {
|
||||
...config
|
||||
...config,
|
||||
};
|
||||
|
||||
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 ret = globalScene.addFieldSprite(0, 0, spriteKey);
|
||||
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;
|
||||
};
|
||||
|
||||
const getItemSprite = (spriteKey: string, hasShadow?: boolean, yShadow?: number) => {
|
||||
const icon = globalScene.add.sprite(-19, 2, "items", spriteKey);
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -129,7 +140,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
|
||||
this.shinySparkleSprites = [];
|
||||
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;
|
||||
|
||||
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.setOrigin(0.5, 1);
|
||||
pokemonShinySparkle.setVisible(false);
|
||||
this.shinySparkleSprites.push({ sprite: pokemonShinySparkle, variant: variant ?? 0 });
|
||||
this.shinySparkleSprites.push({
|
||||
sprite: pokemonShinySparkle,
|
||||
variant: variant ?? 0,
|
||||
});
|
||||
shinySparkleSprites.add(pokemonShinySparkle);
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +230,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
}
|
||||
|
||||
const shinyPromises: Promise<void>[] = [];
|
||||
this.spriteConfigs.forEach((config) => {
|
||||
this.spriteConfigs.forEach(config => {
|
||||
if (config.isPokemon) {
|
||||
globalScene.loadPokemonAtlas(config.spriteKey, config.fileRoot);
|
||||
if (config.isShiny) {
|
||||
|
@ -230,7 +244,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
});
|
||||
|
||||
globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => {
|
||||
this.spriteConfigs.every((config) => {
|
||||
this.spriteConfigs.every(config => {
|
||||
if (config.isItem) {
|
||||
return true;
|
||||
}
|
||||
|
@ -238,17 +252,21 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
const originalWarn = console.warn;
|
||||
|
||||
// Ignore warnings for missing frames, because there will be a lot
|
||||
console.warn = () => {
|
||||
};
|
||||
const frameNames = globalScene.anims.generateFrameNames(config.spriteKey, { zeroPad: 4, suffix: ".png", start: 1, end: 128 });
|
||||
console.warn = () => {};
|
||||
const frameNames = globalScene.anims.generateFrameNames(config.spriteKey, {
|
||||
zeroPad: 4,
|
||||
suffix: ".png",
|
||||
start: 1,
|
||||
end: 128,
|
||||
});
|
||||
|
||||
console.warn = originalWarn;
|
||||
if (!(globalScene.anims.exists(config.spriteKey))) {
|
||||
if (!globalScene.anims.exists(config.spriteKey)) {
|
||||
globalScene.anims.create({
|
||||
key: config.spriteKey,
|
||||
frames: frameNames,
|
||||
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}
|
||||
* @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
|
||||
if (sprite.texture.key === "__MISSING") {
|
||||
console.error(`No texture found for '${animConfig.key}'!`);
|
||||
|
@ -359,7 +381,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
const trainerAnimConfig: PlayAnimationConfig = {
|
||||
key: config.spriteKey,
|
||||
repeat: config?.repeat ? -1 : 0,
|
||||
startFrame: config?.startFrame ?? 0
|
||||
startFrame: config?.startFrame ?? 0,
|
||||
};
|
||||
|
||||
this.tryPlaySprite(sprites[i], tintSprites[i], trainerAnimConfig);
|
||||
|
@ -392,7 +414,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
}
|
||||
|
||||
const ret: Phaser.GameObjects.Sprite[] = [];
|
||||
this.spriteConfigs.forEach((config, i) => {
|
||||
this.spriteConfigs.forEach((_, i) => {
|
||||
ret.push(this.getAt(i * 2));
|
||||
});
|
||||
return ret;
|
||||
|
@ -407,7 +429,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
}
|
||||
|
||||
const ret: Phaser.GameObjects.Sprite[] = [];
|
||||
this.spriteConfigs.forEach((config, i) => {
|
||||
this.spriteConfigs.forEach((_, i) => {
|
||||
ret.push(this.getAt(i * 2 + 1));
|
||||
});
|
||||
|
||||
|
@ -434,7 +456,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
targets: sprite,
|
||||
alpha: alpha || 1,
|
||||
duration: duration,
|
||||
ease: ease || "Linear"
|
||||
ease: ease || "Linear",
|
||||
});
|
||||
} else {
|
||||
sprite.setAlpha(alpha);
|
||||
|
@ -471,7 +493,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
onComplete: () => {
|
||||
sprite.setVisible(false);
|
||||
sprite.setAlpha(1);
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
sprite.setVisible(false);
|
||||
|
@ -497,9 +519,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
* @param value - true for visible, false for hidden
|
||||
*/
|
||||
setVisible(value: boolean): this {
|
||||
this.getSprites().forEach(sprite => {
|
||||
for (const sprite of this.getSprites()) {
|
||||
sprite.setVisible(value);
|
||||
});
|
||||
}
|
||||
return super.setVisible(value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,12 +14,14 @@ export default class PokemonSpriteSparkleHandler {
|
|||
to: 1,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
onRepeat: () => this.onLapse()
|
||||
onRepeat: () => this.onLapse(),
|
||||
});
|
||||
}
|
||||
|
||||
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()) {
|
||||
if (!s.pipelineData["teraColor"] || !(s.pipelineData["teraColor"] as number[]).find(c => c)) {
|
||||
continue;
|
||||
|
@ -30,17 +32,21 @@ export default class PokemonSpriteSparkleHandler {
|
|||
if (!(s.parentContainer instanceof Pokemon) || !(s.parentContainer as Pokemon).isTerastallized) {
|
||||
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 texture = s.texture;
|
||||
const [ width, height ] = [ texture.source[0].width, texture.source[0].height ];
|
||||
const [ pixelX, pixelY ] = [ Utils.randInt(width), Utils.randInt(height) ];
|
||||
const [width, height] = [texture.source[0].width, texture.source[0].height];
|
||||
const [pixelX, pixelY] = [Utils.randInt(width), Utils.randInt(height)];
|
||||
const ratioX = s.width / width;
|
||||
const ratioY = s.height / height;
|
||||
const pixel = texture.manager.getPixel(pixelX, pixelY, texture.key, "__BASE");
|
||||
if (pixel?.alpha) {
|
||||
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 [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",
|
||||
);
|
||||
sparkle.pipelineData["ignoreTimeTint"] = s.pipelineData["ignoreTimeTint"];
|
||||
sparkle.setName("sprite-tera-sparkle");
|
||||
sparkle.play("tera_sparkle");
|
||||
|
@ -52,7 +58,7 @@ export default class PokemonSpriteSparkleHandler {
|
|||
|
||||
add(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
|
||||
if (!Array.isArray(sprites)) {
|
||||
sprites = [ sprites ];
|
||||
sprites = [sprites];
|
||||
}
|
||||
for (const s of sprites) {
|
||||
if (this.sprites.has(s)) {
|
||||
|
@ -64,7 +70,7 @@ export default class PokemonSpriteSparkleHandler {
|
|||
|
||||
remove(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
|
||||
if (!Array.isArray(sprites)) {
|
||||
sprites = [ sprites ];
|
||||
sprites = [sprites];
|
||||
}
|
||||
for (const s of sprites) {
|
||||
this.sprites.delete(s);
|
||||
|
|
4338
src/field/pokemon.ts
4338
src/field/pokemon.ts
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue