Merge branch 'beta' into hebrew-pr

This commit is contained in:
Lugiad 2025-03-09 22:32:52 +01:00 committed by GitHub
commit f090768750
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
746 changed files with 74268 additions and 47160 deletions

View File

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

View File

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

4
.gitignore vendored
View File

@ -41,3 +41,7 @@ coverage
/dependency-graph.svg /dependency-graph.svg
/.vs /.vs
# Script outputs
./*.csv

View File

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

View File

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

106
biome.jsonc Normal file
View File

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

View File

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

View File

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

@ -11,7 +11,7 @@ html {
body { body {
margin: 0; margin: 0;
display:flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
background: #484050; background: #484050;
@ -49,16 +49,17 @@ body {
@media (pointer: coarse) { @media (pointer: coarse) {
/* hasTouchscreen() && !isTouchControlsEnabled */ /* hasTouchscreen() && !isTouchControlsEnabled */
body:has(> #touchControls[class=visible]) #app { body:has(> #touchControls[class="visible"]) #app {
align-items: start; align-items: start;
} }
body:has(> #touchControls[class=visible]) #app > div:first-child { body:has(> #touchControls[class="visible"]) #app > div:first-child {
transform-origin: top !important; transform-origin: top !important;
} }
} }
#layout:fullscreen #dpad, #layout:fullscreen { #layout:fullscreen #dpad,
#layout:fullscreen {
bottom: 6rem; bottom: 6rem;
} }
@ -76,7 +77,6 @@ input {
display: none !important; display: none !important;
} }
input:-internal-autofill-selected { input:-internal-autofill-selected {
-webkit-background-clip: text; -webkit-background-clip: text;
background-clip: text; background-clip: text;
@ -91,18 +91,33 @@ input:-internal-autofill-selected {
--controls-padding: 1rem; --controls-padding: 1rem;
--controls-size-with-padding: calc(var(--controls-size) + var(--controls-padding)); --controls-size-with-padding: calc(
--controls-size-with-wide-padding: calc(var(--controls-size) *1.2 + var(--controls-padding)); var(--controls-size) +
var(--controls-padding)
);
--controls-size-with-wide-padding: calc(
var(--controls-size) *
1.2 +
var(--controls-padding)
);
--control-group-extra-size: calc(var(--controls-size) * 0.8); --control-group-extra-size: calc(var(--controls-size) * 0.8);
--control-group-extra-wide-size: calc(var(--controls-size) * 1.2); --control-group-extra-wide-size: calc(var(--controls-size) * 1.2);
--control-group-extra-2-offset: calc(var(--controls-size-with-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2); --control-group-extra-2-offset: calc(
--control-group-extra-1-offset: calc(var(--controls-padding) + (var(--controls-size) - var(--control-group-extra-size)) / 2); var(--controls-size-with-padding) +
(var(--controls-size) - var(--control-group-extra-size)) /
2
);
--control-group-extra-1-offset: calc(
var(--controls-padding) +
(var(--controls-size) - var(--control-group-extra-size)) /
2
);
--small-control-size: calc(var(--controls-size) / 3); --small-control-size: calc(var(--controls-size) / 3);
--rect-control-size: calc(var(--controls-size) * 0.74); --rect-control-size: calc(var(--controls-size) * 0.74);
font-family: 'emerald'; font-family: "emerald";
font-size: var(--controls-size); font-size: var(--controls-size);
text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size); text-shadow: var(--color-dark) var(--text-shadow-size) var(--text-shadow-size);
color: var(--color-light); color: var(--color-light);
@ -146,32 +161,69 @@ input:-internal-autofill-selected {
/* Hide buttons on specific UIs */ /* Hide buttons on specific UIs */
/* Show #apadPreviousTab and #apadNextTab only in settings, except in touch configuration panel */ /* Show #apadPreviousTab and #apadNextTab only in settings, except in touch configuration panel */
#touchControls:not([data-ui-mode^='SETTINGS']) #apadPreviousTab, #touchControls:not([data-ui-mode^="SETTINGS"]) #apadPreviousTab,
#touchControls:not([data-ui-mode^='SETTINGS']) #apadNextTab, #touchControls:not([data-ui-mode^="SETTINGS"]) #apadNextTab,
#touchControls:is(.config-mode) #apadPreviousTab, #touchControls:is(.config-mode) #apadPreviousTab,
#touchControls:is(.config-mode) #apadNextTab { #touchControls:is(.config-mode) #apadNextTab {
display: none; display: none;
} }
/* Show #apadInfo only in battle */ /* Show #apadInfo only in battle */
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']) #apadInfo { #touchControls:not([data-ui-mode="COMMAND"]):not([data-ui-mode="FIGHT"]):not(
[data-ui-mode="BALL"]
):not([data-ui-mode="TARGET_SELECT"])
#apadInfo {
display: none; display: none;
} }
/* Show #apadStats only in battle and shop */ /* Show #apadStats only in battle and shop */
#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']):not([data-ui-mode='MODIFIER_SELECT']) #apadStats { #touchControls:not([data-ui-mode="COMMAND"]):not([data-ui-mode="FIGHT"]):not(
[data-ui-mode="BALL"]
):not([data-ui-mode="TARGET_SELECT"]):not([data-ui-mode="MODIFIER_SELECT"])
#apadStats {
display: none; display: none;
} }
/* Show cycle buttons only on STARTER_SELECT and on touch configuration panel */ /* Show cycle buttons only on STARTER_SELECT and on touch configuration panel */
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX'], [data-ui-mode='POKEDEX_PAGE']) #apadOpenFilters, #touchControls:not(.config-mode):not(
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX'], [data-ui-mode='POKEDEX_PAGE'], [data-ui-mode='RUN_INFO']) #apadCycleForm, [data-ui-mode="STARTER_SELECT"],
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX'], [data-ui-mode='POKEDEX_PAGE'], [data-ui-mode='RUN_INFO']) #apadCycleShiny, [data-ui-mode="POKEDEX"],
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleNature, [data-ui-mode="POKEDEX_PAGE"]
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX_PAGE'], [data-ui-mode='RUN_INFO']) #apadCycleAbility, )
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX_PAGE']) #apadCycleGender, #apadOpenFilters,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX']) #apadCycleTera { #touchControls:not(.config-mode):not(
display: none; [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 */ /* Configuration toolbar */
@ -217,16 +269,18 @@ input:-internal-autofill-selected {
font-size: var(--small-control-size); font-size: var(--small-control-size);
border-radius: 8px; border-radius: 8px;
padding: 2px 8px; padding: 2px 8px;
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3); text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3)
calc(var(--text-shadow-size) / 3);
} }
#configToolbar .button:active { #configToolbar .button:active {
opacity: var(--touch-control-opacity) opacity: var(--touch-control-opacity);
} }
#configToolbar .orientation-label { #configToolbar .orientation-label {
font-size: var(--small-control-size); font-size: var(--small-control-size);
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3); text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3)
calc(var(--text-shadow-size) / 3);
} }
/* dpad */ /* dpad */
@ -270,7 +324,8 @@ input:-internal-autofill-selected {
.apad-small > .apad-label { .apad-small > .apad-label {
font-size: var(--small-control-size); font-size: var(--small-control-size);
text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3) calc(var(--text-shadow-size) / 3); text-shadow: var(--color-dark) calc(var(--text-shadow-size) / 3)
calc(var(--text-shadow-size) / 3);
} }
.apad-rectangle { .apad-rectangle {
@ -320,7 +375,8 @@ input:-internal-autofill-selected {
/* Layout */ /* Layout */
#layout:fullscreen #dpad, #layout:fullscreen #apad { #layout:fullscreen #dpad,
#layout:fullscreen #apad {
bottom: 6rem; bottom: 6rem;
} }
@ -353,55 +409,55 @@ a {
/* Firefox old*/ /* Firefox old*/
@-moz-keyframes blink { @-moz-keyframes blink {
0% { 0% {
opacity:1; opacity: 1;
} }
50% { 50% {
opacity:0; opacity: 0;
} }
100% { 100% {
opacity:1; opacity: 1;
} }
} }
@-webkit-keyframes blink { @-webkit-keyframes blink {
0% { 0% {
opacity:1; opacity: 1;
} }
50% { 50% {
opacity:0; opacity: 0;
} }
100% { 100% {
opacity:1; opacity: 1;
} }
} }
/* IE */ /* IE */
@-ms-keyframes blink { @-ms-keyframes blink {
0% { 0% {
opacity:1; opacity: 1;
} }
50% { 50% {
opacity:0; opacity: 0;
} }
100% { 100% {
opacity:1; opacity: 1;
} }
} }
/* Opera and prob css3 final iteration */ /* Opera and prob css3 final iteration */
@keyframes blink { @keyframes blink {
0% { 0% {
opacity:1; opacity: 1;
} }
50% { 50% {
opacity:0; opacity: 0;
} }
100% { 100% {
opacity:1; opacity: 1;
} }
} }
.blink-image { .blink-image {
-moz-animation: blink normal 4s infinite ease-in-out; /* Firefox */ -moz-animation: blink normal 4s infinite ease-in-out; /* Firefox */
-webkit-animation: blink normal 4s infinite ease-in-out; /* Webkit */ -webkit-animation: blink normal 4s infinite ease-in-out; /* Webkit */
-ms-animation: blink normal 4s infinite ease-in-out; /* IE */ -ms-animation: blink normal 4s infinite ease-in-out; /* IE */
animation: blink normal 4s infinite ease-in-out; /* Opera and prob css3 final iteration */ animation: blink normal 4s infinite ease-in-out; /* Opera and prob css3 final iteration */
} }

View File

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

14189
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,68 +1,71 @@
{ {
"name": "pokemon-rogue-battle", "name": "pokemon-rogue-battle",
"private": true, "private": true,
"version": "1.7.6", "version": "1.7.7",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "vite", "start": "vite",
"start:dev": "vite --mode development", "start:dev": "vite --mode development",
"build": "vite build", "build": "vite build",
"build:beta": "vite build --mode beta", "build:beta": "vite build --mode beta",
"preview": "vite preview", "preview": "vite preview",
"test": "vitest run --project pre && vitest run --project main", "test": "vitest run --project pre && vitest run --project main",
"test:cov": "vitest run --project pre && vitest run --project main --coverage", "test:cov": "vitest run --project pre && vitest run --project main --coverage",
"test:watch": "vitest run --project pre && vitest watch --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", "test:silent": "vitest run --project pre && vitest run --project main --silent",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"eslint": "eslint --fix .", "eslint": "eslint --fix .",
"eslint-ci": "eslint .", "eslint-ci": "eslint .",
"docs": "typedoc", "biome": "biome check --write --changed --no-errors-on-unmatched",
"depcruise": "depcruise src", "biome-ci": "biome ci --diagnostic-level=error --reporter=github --changed --no-errors-on-unmatched",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg", "docs": "typedoc",
"create-test": "node ./create-test-boilerplate.js", "depcruise": "depcruise src",
"postinstall": "npx lefthook install && npx lefthook run post-merge", "depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
"update-version:patch": "npm version patch --force --no-git-tag-version", "create-test": "node ./create-test-boilerplate.js",
"update-version:minor": "npm version minor --force --no-git-tag-version", "postinstall": "npx lefthook install && npx lefthook run post-merge",
"update-locales:remote": "git submodule update --progress --init --recursive --force --remote" "update-version:patch": "npm version patch --force --no-git-tag-version",
}, "update-version:minor": "npm version minor --force --no-git-tag-version",
"devDependencies": { "update-locales:remote": "git submodule update --progress --init --recursive --force --remote"
"@eslint/js": "^9.3.0", },
"@hpcc-js/wasm": "^2.18.0", "devDependencies": {
"@stylistic/eslint-plugin-ts": "^2.6.0-beta.0", "@biomejs/biome": "1.9.4",
"@types/jsdom": "^21.1.7", "@eslint/js": "^9.3.0",
"@types/node": "^20.12.13", "@hpcc-js/wasm": "^2.18.0",
"@typescript-eslint/eslint-plugin": "^8.0.0-alpha.54", "@stylistic/eslint-plugin-ts": "^2.6.0-beta.0",
"@typescript-eslint/parser": "^8.0.0-alpha.54", "@types/jsdom": "^21.1.7",
"@vitest/coverage-istanbul": "^2.1.9", "@types/node": "^20.12.13",
"dependency-cruiser": "^16.3.10", "@typescript-eslint/eslint-plugin": "^8.0.0-alpha.54",
"eslint": "^9.7.0", "@typescript-eslint/parser": "^8.0.0-alpha.54",
"eslint-plugin-import-x": "^4.2.1", "@vitest/coverage-istanbul": "^2.1.9",
"inquirer": "^11.0.2", "dependency-cruiser": "^16.3.10",
"jsdom": "^24.0.0", "eslint": "^9.7.0",
"lefthook": "^1.6.12", "eslint-plugin-import-x": "^4.2.1",
"msw": "^2.4.9", "inquirer": "^11.0.2",
"phaser3spectorjs": "^0.0.8", "jsdom": "^24.0.0",
"typedoc": "^0.26.4", "lefthook": "^1.6.12",
"typescript": "^5.5.3", "msw": "^2.4.9",
"typescript-eslint": "^8.0.0-alpha.54", "phaser3spectorjs": "^0.0.8",
"vite": "^5.4.14", "typedoc": "^0.26.4",
"vite-tsconfig-paths": "^4.3.2", "typescript": "^5.5.3",
"vitest": "^2.1.9", "typescript-eslint": "^8.0.0-alpha.54",
"vitest-canvas-mock": "^0.3.3" "vite": "^5.4.14",
}, "vite-tsconfig-paths": "^4.3.2",
"dependencies": { "vitest": "^2.1.9",
"@material/material-color-utilities": "^0.2.7", "vitest-canvas-mock": "^0.3.3"
"crypto-js": "^4.2.0", },
"i18next": "^23.11.1", "dependencies": {
"i18next-browser-languagedetector": "^7.2.1", "@material/material-color-utilities": "^0.2.7",
"i18next-http-backend": "^2.6.1", "crypto-js": "^4.2.0",
"i18next-korean-postposition-processor": "^1.0.0", "i18next": "^23.11.1",
"json-stable-stringify": "^1.1.0", "i18next-browser-languagedetector": "^7.2.1",
"jszip": "^3.10.1", "i18next-http-backend": "^2.6.1",
"phaser": "^3.70.0", "i18next-korean-postposition-processor": "^1.0.0",
"phaser3-rex-plugins": "^1.1.84" "json-stable-stringify": "^1.1.0",
}, "jszip": "^3.10.1",
"engines": { "phaser": "^3.70.0",
"node": ">=20.0.0" "phaser3-rex-plugins": "^1.1.84"
} },
"engines": {
"node": ">=20.0.0"
}
} }

View File

@ -183,6 +183,8 @@
"487-origin", "487-origin",
"531-mega", "531-mega",
"531-mega", "531-mega",
"569-gigantamax",
"569-gigantamax",
"6-mega", "6-mega",
"6-mega", "6-mega",
"6-mega-x", "6-mega-x",
@ -339,7 +341,6 @@
"6724", "6724",
"673", "673",
"673", "673",
"675", "675",
"675", "675",
"676", "676",
@ -382,14 +383,12 @@
"692", "692",
"693", "693",
"693", "693",
"695", "695",
"695", "695",
"696", "696",
"696", "696",
"697", "697",
"697", "697",
"699", "699",
"699", "699",
"700", "700",
@ -398,7 +397,6 @@
"701", "701",
"702", "702",
"702", "702",
"704", "704",
"704", "704",
"705", "705",
@ -697,6 +695,8 @@
"814", "814",
"815", "815",
"815", "815",
"815-gigantamax",
"815-gigantamax",
"816", "816",
"816", "816",
"817", "817",
@ -747,6 +747,8 @@
"838", "838",
"839", "839",
"839", "839",
"839-gigantamax",
"839-gigantamax",
"840", "840",
"840", "840",
"841", "841",
@ -1071,8 +1073,6 @@
"978-droopy", "978-droopy",
"978-stretchy", "978-stretchy",
"978-stretchy", "978-stretchy",
"979",
"979",
"980", "980",
"980", "980",
"981", "981",
@ -1301,6 +1301,8 @@
"487b-origin", "487b-origin",
"531b-mega", "531b-mega",
"531b-mega", "531b-mega",
"569b-gigantamax",
"569b-gigantamax",
"6b-mega", "6b-mega",
"6b-mega", "6b-mega",
"6b-mega-x", "6b-mega-x",
@ -1457,7 +1459,6 @@
"6724b", "6724b",
"673b", "673b",
"673b", "673b",
"675b", "675b",
"675b", "675b",
"676b", "676b",
@ -1500,14 +1501,12 @@
"692b", "692b",
"693b", "693b",
"693b", "693b",
"695b", "695b",
"695b", "695b",
"696b", "696b",
"696b", "696b",
"697b", "697b",
"697b", "697b",
"699b", "699b",
"699b", "699b",
"700b", "700b",
@ -1516,7 +1515,6 @@
"701b", "701b",
"702b", "702b",
"702b", "702b",
"704b", "704b",
"704b", "704b",
"705b", "705b",
@ -1815,6 +1813,8 @@
"814b", "814b",
"815b", "815b",
"815b", "815b",
"815b-gigantamax",
"815b-gigantamax",
"816b", "816b",
"816b", "816b",
"817b", "817b",
@ -1865,6 +1865,8 @@
"838b", "838b",
"839b", "839b",
"839b", "839b",
"839b-gigantamax",
"839b-gigantamax",
"840b", "840b",
"840b", "840b",
"841b", "841b",
@ -2191,8 +2193,6 @@
"978b-droopy", "978b-droopy",
"978b-stretchy", "978b-stretchy",
"978b-stretchy", "978b-stretchy",
"979b",
"979b",
"980b", "980b",
"980b", "980b",
"981b", "981b",
@ -2421,6 +2421,8 @@
"487sb-origin", "487sb-origin",
"531sb-mega", "531sb-mega",
"531sb-mega", "531sb-mega",
"569sb-gigantamax",
"569sb-gigantamax",
"6sb-mega", "6sb-mega",
"6sb-mega", "6sb-mega",
"6sb-mega-x", "6sb-mega-x",
@ -2577,7 +2579,6 @@
"6724sb", "6724sb",
"673sb", "673sb",
"673sb", "673sb",
"675sb", "675sb",
"675sb", "675sb",
"676sb", "676sb",
@ -2620,14 +2621,12 @@
"692sb", "692sb",
"693sb", "693sb",
"693sb", "693sb",
"695sb", "695sb",
"695sb", "695sb",
"696sb", "696sb",
"696sb", "696sb",
"697sb", "697sb",
"697sb", "697sb",
"699sb", "699sb",
"699sb", "699sb",
"700sb", "700sb",
@ -2636,7 +2635,6 @@
"701sb", "701sb",
"702sb", "702sb",
"702sb", "702sb",
"704sb", "704sb",
"704sb", "704sb",
"705sb", "705sb",
@ -2935,6 +2933,8 @@
"814sb", "814sb",
"815sb", "815sb",
"815sb", "815sb",
"815sb-gigantamax",
"815sb-gigantamax",
"816sb", "816sb",
"816sb", "816sb",
"817sb", "817sb",
@ -2985,6 +2985,8 @@
"838sb", "838sb",
"839sb", "839sb",
"839sb", "839sb",
"839sb-gigantamax",
"839sb-gigantamax",
"840sb", "840sb",
"840sb", "840sb",
"841sb", "841sb",
@ -3311,8 +3313,6 @@
"978sb-droopy", "978sb-droopy",
"978sb-stretchy", "978sb-stretchy",
"978sb-stretchy", "978sb-stretchy",
"979sb",
"979sb",
"980sb", "980sb",
"980sb", "980sb",
"981sb", "981sb",
@ -3546,6 +3546,8 @@
"487s-origin", "487s-origin",
"531s-mega", "531s-mega",
"531s-mega", "531s-mega",
"569s-gigantamax",
"569s-gigantamax",
"6s-mega", "6s-mega",
"6s-mega", "6s-mega",
"6s-mega-x", "6s-mega-x",
@ -3702,7 +3704,6 @@
"6724s", "6724s",
"673s", "673s",
"673s", "673s",
"675s", "675s",
"675s", "675s",
"676s", "676s",
@ -3745,14 +3746,12 @@
"692s", "692s",
"693s", "693s",
"693s", "693s",
"695s", "695s",
"695s", "695s",
"696s", "696s",
"696s", "696s",
"697s", "697s",
"697s", "697s",
"699s", "699s",
"699s", "699s",
"700s", "700s",
@ -3761,7 +3760,6 @@
"701s", "701s",
"702s", "702s",
"702s", "702s",
"704s", "704s",
"704s", "704s",
"705s", "705s",
@ -4060,6 +4058,8 @@
"814s", "814s",
"815s", "815s",
"815s", "815s",
"815s-gigantamax",
"815s-gigantamax",
"816s", "816s",
"816s", "816s",
"817s", "817s",
@ -4110,6 +4110,8 @@
"838s", "838s",
"839s", "839s",
"839s", "839s",
"839s-gigantamax",
"839s-gigantamax",
"840s", "840s",
"840s", "840s",
"841s", "841s",
@ -4436,8 +4438,6 @@
"978s-droopy", "978s-droopy",
"978s-stretchy", "978s-stretchy",
"978s-stretchy", "978s-stretchy",
"979s",
"979s",
"980s", "980s",
"980s", "980s",
"981s", "981s",

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,659 @@
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 343, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0002.png",
"frame": { "x": 357, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0003.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0004.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0005.png",
"frame": { "x": 85, "y": 292, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 4, "w": 82, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0006.png",
"frame": { "x": 252, "y": 482, "w": 81, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 81, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0007.png",
"frame": { "x": 258, "y": 290, "w": 83, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 5, "w": 83, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0008.png",
"frame": { "x": 416, "y": 484, "w": 79, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 79, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0009.png",
"frame": { "x": 426, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 3, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0010.png",
"frame": { "x": 445, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0011.png",
"frame": { "x": 179, "y": 97, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0012.png",
"frame": { "x": 1, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 2, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0013.png",
"frame": { "x": 523, "y": 195, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 2, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0014.png",
"frame": { "x": 509, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0015.png",
"frame": { "x": 89, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0016.png",
"frame": { "x": 531, "y": 98, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0017.png",
"frame": { "x": 343, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0018.png",
"frame": { "x": 357, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0019.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0020.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0021.png",
"frame": { "x": 85, "y": 292, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 4, "w": 82, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0022.png",
"frame": { "x": 252, "y": 482, "w": 81, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 81, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0023.png",
"frame": { "x": 258, "y": 290, "w": 83, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 5, "w": 83, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0024.png",
"frame": { "x": 416, "y": 484, "w": 79, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 79, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0025.png",
"frame": { "x": 426, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 3, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0026.png",
"frame": { "x": 445, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0027.png",
"frame": { "x": 179, "y": 97, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0028.png",
"frame": { "x": 1, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 2, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0029.png",
"frame": { "x": 523, "y": 195, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 2, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0030.png",
"frame": { "x": 509, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0031.png",
"frame": { "x": 89, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0032.png",
"frame": { "x": 531, "y": 98, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0033.png",
"frame": { "x": 343, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0034.png",
"frame": { "x": 357, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0035.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0036.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0037.png",
"frame": { "x": 85, "y": 292, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 4, "w": 82, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0038.png",
"frame": { "x": 252, "y": 482, "w": 81, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 81, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0039.png",
"frame": { "x": 258, "y": 290, "w": 83, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 5, "w": 83, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0040.png",
"frame": { "x": 416, "y": 484, "w": 79, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 79, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0041.png",
"frame": { "x": 426, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 3, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0042.png",
"frame": { "x": 445, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0043.png",
"frame": { "x": 179, "y": 97, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0044.png",
"frame": { "x": 1, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 2, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0045.png",
"frame": { "x": 523, "y": 195, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 2, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0046.png",
"frame": { "x": 509, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0047.png",
"frame": { "x": 89, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0048.png",
"frame": { "x": 531, "y": 98, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0049.png",
"frame": { "x": 258, "y": 385, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0050.png",
"frame": { "x": 267, "y": 97, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0051.png",
"frame": { "x": 1, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0052.png",
"frame": { "x": 268, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0053.png",
"frame": { "x": 426, "y": 389, "w": 82, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 82, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0054.png",
"frame": { "x": 1, "y": 484, "w": 81, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 6, "w": 81, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0055.png",
"frame": { "x": 341, "y": 389, "w": 83, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 7, "w": 83, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0056.png",
"frame": { "x": 84, "y": 485, "w": 79, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 9, "w": 79, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0057.png",
"frame": { "x": 167, "y": 484, "w": 81, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 7, "w": 81, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0058.png",
"frame": { "x": 1, "y": 195, "w": 86, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 5, "w": 86, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0059.png",
"frame": { "x": 265, "y": 194, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 3, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0060.png",
"frame": { "x": 89, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0061.png",
"frame": { "x": 1, "y": 290, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0062.png",
"frame": { "x": 1, "y": 387, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 1, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0063.png",
"frame": { "x": 353, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0064.png",
"frame": { "x": 510, "y": 389, "w": 80, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 1, "w": 80, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0065.png",
"frame": { "x": 169, "y": 387, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 1, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0066.png",
"frame": { "x": 355, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 14, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0067.png",
"frame": { "x": 443, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 14, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0068.png",
"frame": { "x": 90, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 3, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0069.png",
"frame": { "x": 174, "y": 290, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0070.png",
"frame": { "x": 84, "y": 388, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 4, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0071.png",
"frame": { "x": 438, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 3, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0072.png",
"frame": { "x": 335, "y": 483, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.13-x64",
"image": "815-gigantamax.png",
"format": "I8",
"size": { "w": 611, "h": 579 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,821 @@
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 525, "y": 567, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0002.png",
"frame": { "x": 441, "y": 566, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0003.png",
"frame": { "x": 609, "y": 567, "w": 83, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 83, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0004.png",
"frame": { "x": 255, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0005.png",
"frame": { "x": 171, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0006.png",
"frame": { "x": 0, "y": 569, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0007.png",
"frame": { "x": 339, "y": 569, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 1, "w": 82, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0008.png",
"frame": { "x": 0, "y": 474, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0009.png",
"frame": { "x": 464, "y": 375, "w": 89, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 1, "w": 89, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0010.png",
"frame": { "x": 580, "y": 190, "w": 92, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 1, "w": 92, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0011.png",
"frame": { "x": 95, "y": 96, "w": 94, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 1, "w": 94, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0012.png",
"frame": { "x": 198, "y": 95, "w": 95, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 95, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0013.png",
"frame": { "x": 491, "y": 0, "w": 96, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 96, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0014.png",
"frame": { "x": 297, "y": 0, "w": 98, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 98, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0015.png",
"frame": { "x": 0, "y": 0, "w": 100, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 1, "w": 100, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0016.png",
"frame": { "x": 198, "y": 0, "w": 99, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 99, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0017.png",
"frame": { "x": 100, "y": 0, "w": 98, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 0, "w": 98, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0018.png",
"frame": { "x": 395, "y": 0, "w": 96, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 0, "w": 96, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0019.png",
"frame": { "x": 189, "y": 190, "w": 92, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 0, "w": 92, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0020.png",
"frame": { "x": 89, "y": 379, "w": 88, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 0, "w": 88, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0021.png",
"frame": { "x": 525, "y": 567, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0022.png",
"frame": { "x": 441, "y": 566, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0023.png",
"frame": { "x": 609, "y": 567, "w": 83, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 83, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0024.png",
"frame": { "x": 255, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0025.png",
"frame": { "x": 171, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0026.png",
"frame": { "x": 0, "y": 569, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0027.png",
"frame": { "x": 339, "y": 569, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 1, "w": 82, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0028.png",
"frame": { "x": 0, "y": 474, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0029.png",
"frame": { "x": 464, "y": 375, "w": 89, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 1, "w": 89, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0030.png",
"frame": { "x": 580, "y": 190, "w": 92, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 1, "w": 92, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0031.png",
"frame": { "x": 95, "y": 96, "w": 94, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 1, "w": 94, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0032.png",
"frame": { "x": 198, "y": 95, "w": 95, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 95, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0033.png",
"frame": { "x": 491, "y": 0, "w": 96, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 96, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0034.png",
"frame": { "x": 297, "y": 0, "w": 98, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 98, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0035.png",
"frame": { "x": 0, "y": 0, "w": 100, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 1, "w": 100, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0036.png",
"frame": { "x": 198, "y": 0, "w": 99, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 99, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0037.png",
"frame": { "x": 100, "y": 0, "w": 98, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 0, "w": 98, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0038.png",
"frame": { "x": 395, "y": 0, "w": 96, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 0, "w": 96, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0039.png",
"frame": { "x": 189, "y": 190, "w": 92, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 0, "w": 92, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0040.png",
"frame": { "x": 89, "y": 379, "w": 88, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 0, "w": 88, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0041.png",
"frame": { "x": 525, "y": 567, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0042.png",
"frame": { "x": 441, "y": 566, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0043.png",
"frame": { "x": 609, "y": 567, "w": 83, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 83, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0044.png",
"frame": { "x": 255, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0045.png",
"frame": { "x": 171, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0046.png",
"frame": { "x": 0, "y": 569, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0047.png",
"frame": { "x": 339, "y": 569, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 1, "w": 82, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0048.png",
"frame": { "x": 0, "y": 474, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0049.png",
"frame": { "x": 464, "y": 375, "w": 89, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 1, "w": 89, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0050.png",
"frame": { "x": 580, "y": 190, "w": 92, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 1, "w": 92, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0051.png",
"frame": { "x": 95, "y": 96, "w": 94, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 1, "w": 94, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0052.png",
"frame": { "x": 198, "y": 95, "w": 95, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 95, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0053.png",
"frame": { "x": 491, "y": 0, "w": 96, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 96, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0054.png",
"frame": { "x": 297, "y": 0, "w": 98, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 98, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0055.png",
"frame": { "x": 0, "y": 0, "w": 100, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 1, "w": 100, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0056.png",
"frame": { "x": 198, "y": 0, "w": 99, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 99, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0057.png",
"frame": { "x": 100, "y": 0, "w": 98, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 0, "w": 98, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0058.png",
"frame": { "x": 395, "y": 0, "w": 96, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 0, "w": 96, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0059.png",
"frame": { "x": 189, "y": 190, "w": 92, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 0, "w": 92, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0060.png",
"frame": { "x": 89, "y": 379, "w": 88, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 0, "w": 88, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0061.png",
"frame": { "x": 525, "y": 567, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0062.png",
"frame": { "x": 454, "y": 470, "w": 86, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 86, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0063.png",
"frame": { "x": 0, "y": 379, "w": 89, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 1, "w": 89, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0064.png",
"frame": { "x": 573, "y": 285, "w": 91, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 1, "w": 91, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0065.png",
"frame": { "x": 93, "y": 191, "w": 92, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 15, "y": 1, "w": 92, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0066.png",
"frame": { "x": 390, "y": 96, "w": 94, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 17, "y": 1, "w": 94, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0067.png",
"frame": { "x": 585, "y": 96, "w": 94, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 17, "y": 2, "w": 94, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0068.png",
"frame": { "x": 293, "y": 95, "w": 97, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 14, "y": 3, "w": 97, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0069.png",
"frame": { "x": 484, "y": 190, "w": 96, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 4, "w": 96, "h": 92 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0070.png",
"frame": { "x": 92, "y": 286, "w": 92, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 3, "w": 92, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0071.png",
"frame": { "x": 177, "y": 471, "w": 89, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 89, "h": 92 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0072.png",
"frame": { "x": 177, "y": 379, "w": 91, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 4, "w": 91, "h": 92 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0073.png",
"frame": { "x": 184, "y": 286, "w": 91, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 91, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0074.png",
"frame": { "x": 480, "y": 282, "w": 93, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 93, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0075.png",
"frame": { "x": 281, "y": 281, "w": 93, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 93, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0076.png",
"frame": { "x": 293, "y": 188, "w": 95, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 3, "w": 95, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0077.png",
"frame": { "x": 587, "y": 0, "w": 95, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 0, "w": 95, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0078.png",
"frame": { "x": 0, "y": 95, "w": 95, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 1, "w": 95, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0079.png",
"frame": { "x": 491, "y": 95, "w": 94, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 1, "w": 94, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0080.png",
"frame": { "x": 0, "y": 190, "w": 93, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 1, "w": 93, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0081.png",
"frame": { "x": 388, "y": 191, "w": 92, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 1, "w": 92, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0082.png",
"frame": { "x": 0, "y": 285, "w": 92, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 2, "w": 92, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0083.png",
"frame": { "x": 275, "y": 374, "w": 90, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 2, "w": 90, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0084.png",
"frame": { "x": 374, "y": 286, "w": 90, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 2, "w": 90, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0085.png",
"frame": { "x": 365, "y": 380, "w": 89, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 2, "w": 89, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0086.png",
"frame": { "x": 553, "y": 380, "w": 89, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 3, "w": 89, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0087.png",
"frame": { "x": 540, "y": 473, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 87, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0088.png",
"frame": { "x": 268, "y": 468, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 1, "w": 87, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0089.png",
"frame": { "x": 355, "y": 474, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0090.png",
"frame": { "x": 86, "y": 475, "w": 85, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 0, "w": 85, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.13-x64",
"image": "839-gigantamax.png",
"format": "I8",
"size": { "w": 692, "h": 664 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,659 @@
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 525, "y": 384, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0002.png",
"frame": { "x": 356, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0003.png",
"frame": { "x": 444, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0004.png",
"frame": { "x": 267, "y": 98, "w": 87, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 87, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0005.png",
"frame": { "x": 528, "y": 194, "w": 82, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 6, "w": 82, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0006.png",
"frame": { "x": 250, "y": 484, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0007.png",
"frame": { "x": 333, "y": 388, "w": 83, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 6, "w": 83, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0008.png",
"frame": { "x": 82, "y": 485, "w": 79, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 7, "w": 79, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0009.png",
"frame": { "x": 167, "y": 483, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0010.png",
"frame": { "x": 440, "y": 194, "w": 86, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 5, "w": 86, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0011.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0012.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 3, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0013.png",
"frame": { "x": 267, "y": 193, "w": 85, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 85, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0014.png",
"frame": { "x": 1, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0015.png",
"frame": { "x": 86, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0016.png",
"frame": { "x": 333, "y": 482, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0017.png",
"frame": { "x": 525, "y": 384, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0018.png",
"frame": { "x": 356, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0019.png",
"frame": { "x": 444, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0020.png",
"frame": { "x": 267, "y": 98, "w": 87, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 87, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0021.png",
"frame": { "x": 528, "y": 194, "w": 82, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 6, "w": 82, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0022.png",
"frame": { "x": 250, "y": 484, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0023.png",
"frame": { "x": 333, "y": 388, "w": 83, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 6, "w": 83, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0024.png",
"frame": { "x": 82, "y": 485, "w": 79, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 7, "w": 79, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0025.png",
"frame": { "x": 167, "y": 483, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0026.png",
"frame": { "x": 440, "y": 194, "w": 86, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 5, "w": 86, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0027.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0028.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 3, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0029.png",
"frame": { "x": 267, "y": 193, "w": 85, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 85, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0030.png",
"frame": { "x": 1, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0031.png",
"frame": { "x": 86, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0032.png",
"frame": { "x": 333, "y": 482, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0033.png",
"frame": { "x": 525, "y": 384, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0034.png",
"frame": { "x": 356, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0035.png",
"frame": { "x": 444, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0036.png",
"frame": { "x": 267, "y": 98, "w": 87, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 87, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0037.png",
"frame": { "x": 528, "y": 194, "w": 82, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 6, "w": 82, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0038.png",
"frame": { "x": 250, "y": 484, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0039.png",
"frame": { "x": 333, "y": 388, "w": 83, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 6, "w": 83, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0040.png",
"frame": { "x": 82, "y": 485, "w": 79, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 7, "w": 79, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0041.png",
"frame": { "x": 167, "y": 483, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0042.png",
"frame": { "x": 440, "y": 194, "w": 86, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 5, "w": 86, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0043.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0044.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 3, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0045.png",
"frame": { "x": 267, "y": 193, "w": 85, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 85, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0046.png",
"frame": { "x": 1, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0047.png",
"frame": { "x": 86, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0048.png",
"frame": { "x": 333, "y": 482, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0049.png",
"frame": { "x": 84, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0050.png",
"frame": { "x": 179, "y": 97, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0051.png",
"frame": { "x": 444, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0052.png",
"frame": { "x": 1, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0053.png",
"frame": { "x": 171, "y": 289, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 2, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0054.png",
"frame": { "x": 1, "y": 389, "w": 81, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 4, "w": 81, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0055.png",
"frame": { "x": 525, "y": 288, "w": 83, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 83, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0056.png",
"frame": { "x": 1, "y": 485, "w": 79, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 7, "w": 79, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0057.png",
"frame": { "x": 84, "y": 389, "w": 81, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 6, "w": 81, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0058.png",
"frame": { "x": 356, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 4, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0059.png",
"frame": { "x": 268, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 3, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0060.png",
"frame": { "x": 354, "y": 194, "w": 84, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 15, "y": 2, "w": 84, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0061.png",
"frame": { "x": 255, "y": 290, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 1, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0062.png",
"frame": { "x": 423, "y": 385, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 15, "y": 1, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0063.png",
"frame": { "x": 1, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0064.png",
"frame": { "x": 506, "y": 481, "w": 80, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 80, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0065.png",
"frame": { "x": 250, "y": 387, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 1, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0066.png",
"frame": { "x": 1, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0067.png",
"frame": { "x": 89, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0068.png",
"frame": { "x": 90, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 3, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0069.png",
"frame": { "x": 339, "y": 291, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 5, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0070.png",
"frame": { "x": 167, "y": 386, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 4, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0071.png",
"frame": { "x": 440, "y": 288, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 3, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0072.png",
"frame": { "x": 414, "y": 482, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.13-x64",
"image": "815-gigantamax.png",
"format": "I8",
"size": { "w": 611, "h": 579 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,821 @@
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 238, "y": 282, "w": 77, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 77, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0002.png",
"frame": { "x": 470, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 19, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0003.png",
"frame": { "x": 546, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0004.png",
"frame": { "x": 469, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0005.png",
"frame": { "x": 544, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0006.png",
"frame": { "x": 154, "y": 472, "w": 76, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 1, "w": 76, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0007.png",
"frame": { "x": 0, "y": 566, "w": 75, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 75, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0008.png",
"frame": { "x": 393, "y": 376, "w": 77, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 77, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0009.png",
"frame": { "x": 414, "y": 190, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0010.png",
"frame": { "x": 492, "y": 191, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0011.png",
"frame": { "x": 159, "y": 283, "w": 78, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 25, "y": 2, "w": 78, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0012.png",
"frame": { "x": 158, "y": 189, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0013.png",
"frame": { "x": 255, "y": 188, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0014.png",
"frame": { "x": 173, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0015.png",
"frame": { "x": 90, "y": 93, "w": 83, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 83, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0016.png",
"frame": { "x": 350, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0017.png",
"frame": { "x": 524, "y": 0, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0018.png",
"frame": { "x": 268, "y": 92, "w": 82, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 82, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0019.png",
"frame": { "x": 513, "y": 95, "w": 80, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 80, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0020.png",
"frame": { "x": 0, "y": 185, "w": 79, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 79, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0021.png",
"frame": { "x": 238, "y": 282, "w": 77, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 77, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0022.png",
"frame": { "x": 470, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 19, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0023.png",
"frame": { "x": 546, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0024.png",
"frame": { "x": 469, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0025.png",
"frame": { "x": 544, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0026.png",
"frame": { "x": 154, "y": 472, "w": 76, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 1, "w": 76, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0027.png",
"frame": { "x": 0, "y": 566, "w": 75, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 75, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0028.png",
"frame": { "x": 393, "y": 376, "w": 77, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 77, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0029.png",
"frame": { "x": 414, "y": 190, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0030.png",
"frame": { "x": 492, "y": 191, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0031.png",
"frame": { "x": 159, "y": 283, "w": 78, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 25, "y": 2, "w": 78, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0032.png",
"frame": { "x": 158, "y": 189, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0033.png",
"frame": { "x": 255, "y": 188, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0034.png",
"frame": { "x": 173, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0035.png",
"frame": { "x": 90, "y": 93, "w": 83, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 83, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0036.png",
"frame": { "x": 350, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0037.png",
"frame": { "x": 524, "y": 0, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0038.png",
"frame": { "x": 268, "y": 92, "w": 82, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 82, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0039.png",
"frame": { "x": 513, "y": 95, "w": 80, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 80, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0040.png",
"frame": { "x": 0, "y": 185, "w": 79, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 79, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0041.png",
"frame": { "x": 238, "y": 282, "w": 77, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 77, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0042.png",
"frame": { "x": 470, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 19, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0043.png",
"frame": { "x": 546, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0044.png",
"frame": { "x": 469, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0045.png",
"frame": { "x": 544, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0046.png",
"frame": { "x": 154, "y": 472, "w": 76, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 1, "w": 76, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0047.png",
"frame": { "x": 0, "y": 566, "w": 75, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 75, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0048.png",
"frame": { "x": 393, "y": 376, "w": 77, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 77, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0049.png",
"frame": { "x": 414, "y": 190, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0050.png",
"frame": { "x": 492, "y": 191, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0051.png",
"frame": { "x": 159, "y": 283, "w": 78, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 25, "y": 2, "w": 78, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0052.png",
"frame": { "x": 158, "y": 189, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0053.png",
"frame": { "x": 255, "y": 188, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0054.png",
"frame": { "x": 173, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0055.png",
"frame": { "x": 90, "y": 93, "w": 83, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 83, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0056.png",
"frame": { "x": 350, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0057.png",
"frame": { "x": 524, "y": 0, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0058.png",
"frame": { "x": 268, "y": 92, "w": 82, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 82, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0059.png",
"frame": { "x": 513, "y": 95, "w": 80, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 80, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0060.png",
"frame": { "x": 0, "y": 185, "w": 79, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 79, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0061.png",
"frame": { "x": 238, "y": 282, "w": 77, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 77, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0062.png",
"frame": { "x": 79, "y": 187, "w": 79, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 17, "y": 0, "w": 79, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0063.png",
"frame": { "x": 432, "y": 95, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 1, "w": 81, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0064.png",
"frame": { "x": 441, "y": 0, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0065.png",
"frame": { "x": 356, "y": 0, "w": 85, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 1, "w": 85, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0066.png",
"frame": { "x": 180, "y": 0, "w": 88, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 88, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0067.png",
"frame": { "x": 0, "y": 0, "w": 90, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 2, "w": 90, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0068.png",
"frame": { "x": 90, "y": 0, "w": 90, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 3, "w": 90, "h": 93 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0069.png",
"frame": { "x": 268, "y": 0, "w": 88, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 4, "w": 88, "h": 92 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0070.png",
"frame": { "x": 0, "y": 94, "w": 85, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 5, "w": 85, "h": 91 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0071.png",
"frame": { "x": 77, "y": 283, "w": 82, "h": 90 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 6, "w": 82, "h": 90 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0072.png",
"frame": { "x": 397, "y": 285, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 5, "w": 81, "h": 91 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0073.png",
"frame": { "x": 232, "y": 378, "w": 81, "h": 90 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 6, "w": 81, "h": 90 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0074.png",
"frame": { "x": 315, "y": 283, "w": 82, "h": 90 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 6, "w": 82, "h": 90 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0075.png",
"frame": { "x": 478, "y": 286, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 5, "w": 81, "h": 91 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0076.png",
"frame": { "x": 313, "y": 467, "w": 79, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 4, "w": 79, "h": 92 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0077.png",
"frame": { "x": 335, "y": 189, "w": 79, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 79, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0078.png",
"frame": { "x": 315, "y": 373, "w": 78, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 78, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0079.png",
"frame": { "x": 77, "y": 373, "w": 78, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 78, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0080.png",
"frame": { "x": 77, "y": 467, "w": 77, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 77, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0081.png",
"frame": { "x": 232, "y": 468, "w": 77, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 77, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0082.png",
"frame": { "x": 392, "y": 471, "w": 77, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 77, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0083.png",
"frame": { "x": 309, "y": 559, "w": 77, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 3, "w": 77, "h": 93 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0084.png",
"frame": { "x": 386, "y": 565, "w": 77, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 3, "w": 77, "h": 93 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0085.png",
"frame": { "x": 77, "y": 561, "w": 77, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 3, "w": 77, "h": 93 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0086.png",
"frame": { "x": 230, "y": 562, "w": 77, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 3, "w": 77, "h": 93 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0087.png",
"frame": { "x": 0, "y": 472, "w": 77, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 2, "w": 77, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0088.png",
"frame": { "x": 0, "y": 377, "w": 77, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 1, "w": 77, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0089.png",
"frame": { "x": 155, "y": 377, "w": 77, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 1, "w": 77, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0090.png",
"frame": { "x": 0, "y": 281, "w": 77, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 77, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.13-x64",
"image": "839-gigantamax.png",
"format": "I8",
"size": { "w": 622, "h": 661 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,659 @@
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 525, "y": 384, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0002.png",
"frame": { "x": 356, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0003.png",
"frame": { "x": 444, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0004.png",
"frame": { "x": 267, "y": 98, "w": 87, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 87, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0005.png",
"frame": { "x": 528, "y": 194, "w": 82, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 6, "w": 82, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0006.png",
"frame": { "x": 250, "y": 484, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0007.png",
"frame": { "x": 333, "y": 388, "w": 83, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 6, "w": 83, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0008.png",
"frame": { "x": 82, "y": 485, "w": 79, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 7, "w": 79, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0009.png",
"frame": { "x": 167, "y": 483, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0010.png",
"frame": { "x": 440, "y": 194, "w": 86, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 5, "w": 86, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0011.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0012.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 3, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0013.png",
"frame": { "x": 267, "y": 193, "w": 85, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 85, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0014.png",
"frame": { "x": 1, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0015.png",
"frame": { "x": 86, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0016.png",
"frame": { "x": 333, "y": 482, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0017.png",
"frame": { "x": 525, "y": 384, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0018.png",
"frame": { "x": 356, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0019.png",
"frame": { "x": 444, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0020.png",
"frame": { "x": 267, "y": 98, "w": 87, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 87, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0021.png",
"frame": { "x": 528, "y": 194, "w": 82, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 6, "w": 82, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0022.png",
"frame": { "x": 250, "y": 484, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0023.png",
"frame": { "x": 333, "y": 388, "w": 83, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 6, "w": 83, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0024.png",
"frame": { "x": 82, "y": 485, "w": 79, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 7, "w": 79, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0025.png",
"frame": { "x": 167, "y": 483, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0026.png",
"frame": { "x": 440, "y": 194, "w": 86, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 5, "w": 86, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0027.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0028.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 3, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0029.png",
"frame": { "x": 267, "y": 193, "w": 85, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 85, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0030.png",
"frame": { "x": 1, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0031.png",
"frame": { "x": 86, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0032.png",
"frame": { "x": 333, "y": 482, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0033.png",
"frame": { "x": 525, "y": 384, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0034.png",
"frame": { "x": 356, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0035.png",
"frame": { "x": 444, "y": 98, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0036.png",
"frame": { "x": 267, "y": 98, "w": 87, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 87, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0037.png",
"frame": { "x": 528, "y": 194, "w": 82, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 6, "w": 82, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0038.png",
"frame": { "x": 250, "y": 484, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0039.png",
"frame": { "x": 333, "y": 388, "w": 83, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 6, "w": 83, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0040.png",
"frame": { "x": 82, "y": 485, "w": 79, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 7, "w": 79, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0041.png",
"frame": { "x": 167, "y": 483, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 7, "w": 81, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0042.png",
"frame": { "x": 440, "y": 194, "w": 86, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 5, "w": 86, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0043.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 86, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0044.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 3, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0045.png",
"frame": { "x": 267, "y": 193, "w": 85, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 85, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0046.png",
"frame": { "x": 1, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0047.png",
"frame": { "x": 86, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0048.png",
"frame": { "x": 333, "y": 482, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0049.png",
"frame": { "x": 84, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0050.png",
"frame": { "x": 179, "y": 97, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0051.png",
"frame": { "x": 444, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0052.png",
"frame": { "x": 1, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0053.png",
"frame": { "x": 171, "y": 289, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 2, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0054.png",
"frame": { "x": 1, "y": 389, "w": 81, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 4, "w": 81, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0055.png",
"frame": { "x": 525, "y": 288, "w": 83, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 83, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0056.png",
"frame": { "x": 1, "y": 485, "w": 79, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 7, "w": 79, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0057.png",
"frame": { "x": 84, "y": 389, "w": 81, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 6, "w": 81, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0058.png",
"frame": { "x": 356, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 4, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0059.png",
"frame": { "x": 268, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 3, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0060.png",
"frame": { "x": 354, "y": 194, "w": 84, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 15, "y": 2, "w": 84, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0061.png",
"frame": { "x": 255, "y": 290, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 1, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0062.png",
"frame": { "x": 423, "y": 385, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 15, "y": 1, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0063.png",
"frame": { "x": 1, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0064.png",
"frame": { "x": 506, "y": 481, "w": 80, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 80, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0065.png",
"frame": { "x": 250, "y": 387, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 1, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0066.png",
"frame": { "x": 1, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0067.png",
"frame": { "x": 89, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0068.png",
"frame": { "x": 90, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 3, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0069.png",
"frame": { "x": 339, "y": 291, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 5, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0070.png",
"frame": { "x": 167, "y": 386, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 4, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0071.png",
"frame": { "x": 440, "y": 288, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 3, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0072.png",
"frame": { "x": 414, "y": 482, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.13-x64",
"image": "815-gigantamax.png",
"format": "I8",
"size": { "w": 611, "h": 579 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,821 @@
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 238, "y": 282, "w": 77, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 77, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0002.png",
"frame": { "x": 470, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 19, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0003.png",
"frame": { "x": 546, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0004.png",
"frame": { "x": 469, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0005.png",
"frame": { "x": 544, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0006.png",
"frame": { "x": 154, "y": 472, "w": 76, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 1, "w": 76, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0007.png",
"frame": { "x": 0, "y": 566, "w": 75, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 75, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0008.png",
"frame": { "x": 393, "y": 376, "w": 77, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 77, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0009.png",
"frame": { "x": 414, "y": 190, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0010.png",
"frame": { "x": 492, "y": 191, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0011.png",
"frame": { "x": 159, "y": 283, "w": 78, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 25, "y": 2, "w": 78, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0012.png",
"frame": { "x": 158, "y": 189, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0013.png",
"frame": { "x": 255, "y": 188, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0014.png",
"frame": { "x": 173, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0015.png",
"frame": { "x": 90, "y": 93, "w": 83, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 83, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0016.png",
"frame": { "x": 350, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0017.png",
"frame": { "x": 524, "y": 0, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0018.png",
"frame": { "x": 268, "y": 92, "w": 82, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 82, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0019.png",
"frame": { "x": 513, "y": 95, "w": 80, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 80, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0020.png",
"frame": { "x": 0, "y": 185, "w": 79, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 79, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0021.png",
"frame": { "x": 238, "y": 282, "w": 77, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 77, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0022.png",
"frame": { "x": 470, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 19, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0023.png",
"frame": { "x": 546, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0024.png",
"frame": { "x": 469, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0025.png",
"frame": { "x": 544, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0026.png",
"frame": { "x": 154, "y": 472, "w": 76, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 1, "w": 76, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0027.png",
"frame": { "x": 0, "y": 566, "w": 75, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 75, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0028.png",
"frame": { "x": 393, "y": 376, "w": 77, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 77, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0029.png",
"frame": { "x": 414, "y": 190, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0030.png",
"frame": { "x": 492, "y": 191, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0031.png",
"frame": { "x": 159, "y": 283, "w": 78, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 25, "y": 2, "w": 78, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0032.png",
"frame": { "x": 158, "y": 189, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0033.png",
"frame": { "x": 255, "y": 188, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0034.png",
"frame": { "x": 173, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0035.png",
"frame": { "x": 90, "y": 93, "w": 83, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 83, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0036.png",
"frame": { "x": 350, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0037.png",
"frame": { "x": 524, "y": 0, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0038.png",
"frame": { "x": 268, "y": 92, "w": 82, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 82, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0039.png",
"frame": { "x": 513, "y": 95, "w": 80, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 80, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0040.png",
"frame": { "x": 0, "y": 185, "w": 79, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 79, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0041.png",
"frame": { "x": 238, "y": 282, "w": 77, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 77, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0042.png",
"frame": { "x": 470, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 19, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0043.png",
"frame": { "x": 546, "y": 377, "w": 76, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 76, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0044.png",
"frame": { "x": 469, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0045.png",
"frame": { "x": 544, "y": 473, "w": 75, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 18, "y": 0, "w": 75, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0046.png",
"frame": { "x": 154, "y": 472, "w": 76, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 1, "w": 76, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0047.png",
"frame": { "x": 0, "y": 566, "w": 75, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 75, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0048.png",
"frame": { "x": 393, "y": 376, "w": 77, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 1, "w": 77, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0049.png",
"frame": { "x": 414, "y": 190, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0050.png",
"frame": { "x": 492, "y": 191, "w": 78, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 1, "w": 78, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0051.png",
"frame": { "x": 159, "y": 283, "w": 78, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 25, "y": 2, "w": 78, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0052.png",
"frame": { "x": 158, "y": 189, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0053.png",
"frame": { "x": 255, "y": 188, "w": 80, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 80, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0054.png",
"frame": { "x": 173, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0055.png",
"frame": { "x": 90, "y": 93, "w": 83, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 83, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0056.png",
"frame": { "x": 350, "y": 95, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 82, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0057.png",
"frame": { "x": 524, "y": 0, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0058.png",
"frame": { "x": 268, "y": 92, "w": 82, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 82, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0059.png",
"frame": { "x": 513, "y": 95, "w": 80, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 0, "w": 80, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0060.png",
"frame": { "x": 0, "y": 185, "w": 79, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 79, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0061.png",
"frame": { "x": 238, "y": 282, "w": 77, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 77, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0062.png",
"frame": { "x": 79, "y": 187, "w": 79, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 17, "y": 0, "w": 79, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0063.png",
"frame": { "x": 432, "y": 95, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 1, "w": 81, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0064.png",
"frame": { "x": 441, "y": 0, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0065.png",
"frame": { "x": 356, "y": 0, "w": 85, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 1, "w": 85, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0066.png",
"frame": { "x": 180, "y": 0, "w": 88, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 88, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0067.png",
"frame": { "x": 0, "y": 0, "w": 90, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 2, "w": 90, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0068.png",
"frame": { "x": 90, "y": 0, "w": 90, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 3, "w": 90, "h": 93 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0069.png",
"frame": { "x": 268, "y": 0, "w": 88, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 4, "w": 88, "h": 92 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0070.png",
"frame": { "x": 0, "y": 94, "w": 85, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 5, "w": 85, "h": 91 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0071.png",
"frame": { "x": 77, "y": 283, "w": 82, "h": 90 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 6, "w": 82, "h": 90 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0072.png",
"frame": { "x": 397, "y": 285, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 5, "w": 81, "h": 91 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0073.png",
"frame": { "x": 232, "y": 378, "w": 81, "h": 90 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 6, "w": 81, "h": 90 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0074.png",
"frame": { "x": 315, "y": 283, "w": 82, "h": 90 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 6, "w": 82, "h": 90 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0075.png",
"frame": { "x": 478, "y": 286, "w": 81, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 22, "y": 5, "w": 81, "h": 91 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0076.png",
"frame": { "x": 313, "y": 467, "w": 79, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 4, "w": 79, "h": 92 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0077.png",
"frame": { "x": 335, "y": 189, "w": 79, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 2, "w": 79, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0078.png",
"frame": { "x": 315, "y": 373, "w": 78, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 78, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0079.png",
"frame": { "x": 77, "y": 373, "w": 78, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 78, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0080.png",
"frame": { "x": 77, "y": 467, "w": 77, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 77, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0081.png",
"frame": { "x": 232, "y": 468, "w": 77, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 77, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0082.png",
"frame": { "x": 392, "y": 471, "w": 77, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 24, "y": 2, "w": 77, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0083.png",
"frame": { "x": 309, "y": 559, "w": 77, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 3, "w": 77, "h": 93 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0084.png",
"frame": { "x": 386, "y": 565, "w": 77, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 3, "w": 77, "h": 93 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0085.png",
"frame": { "x": 77, "y": 561, "w": 77, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 3, "w": 77, "h": 93 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0086.png",
"frame": { "x": 230, "y": 562, "w": 77, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 23, "y": 3, "w": 77, "h": 93 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0087.png",
"frame": { "x": 0, "y": 472, "w": 77, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 2, "w": 77, "h": 94 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0088.png",
"frame": { "x": 0, "y": 377, "w": 77, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 1, "w": 77, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0089.png",
"frame": { "x": 155, "y": 377, "w": 77, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 21, "y": 1, "w": 77, "h": 95 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
},
{
"filename": "0090.png",
"frame": { "x": 0, "y": 281, "w": 77, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 20, "y": 0, "w": 77, "h": 96 },
"sourceSize": { "w": 106, "h": 96 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.13-x64",
"image": "839-gigantamax.png",
"format": "I8",
"size": { "w": 622, "h": 661 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,659 @@
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 343, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0002.png",
"frame": { "x": 357, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0003.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0004.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0005.png",
"frame": { "x": 85, "y": 292, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 4, "w": 82, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0006.png",
"frame": { "x": 252, "y": 482, "w": 81, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 81, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0007.png",
"frame": { "x": 258, "y": 290, "w": 83, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 5, "w": 83, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0008.png",
"frame": { "x": 416, "y": 484, "w": 79, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 79, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0009.png",
"frame": { "x": 426, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 3, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0010.png",
"frame": { "x": 445, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0011.png",
"frame": { "x": 179, "y": 97, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0012.png",
"frame": { "x": 1, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 2, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0013.png",
"frame": { "x": 523, "y": 195, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 2, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0014.png",
"frame": { "x": 509, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0015.png",
"frame": { "x": 89, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0016.png",
"frame": { "x": 531, "y": 98, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0017.png",
"frame": { "x": 343, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0018.png",
"frame": { "x": 357, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0019.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0020.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0021.png",
"frame": { "x": 85, "y": 292, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 4, "w": 82, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0022.png",
"frame": { "x": 252, "y": 482, "w": 81, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 81, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0023.png",
"frame": { "x": 258, "y": 290, "w": 83, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 5, "w": 83, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0024.png",
"frame": { "x": 416, "y": 484, "w": 79, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 79, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0025.png",
"frame": { "x": 426, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 3, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0026.png",
"frame": { "x": 445, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0027.png",
"frame": { "x": 179, "y": 97, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0028.png",
"frame": { "x": 1, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 2, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0029.png",
"frame": { "x": 523, "y": 195, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 2, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0030.png",
"frame": { "x": 509, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0031.png",
"frame": { "x": 89, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0032.png",
"frame": { "x": 531, "y": 98, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0033.png",
"frame": { "x": 343, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0034.png",
"frame": { "x": 357, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0035.png",
"frame": { "x": 177, "y": 194, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0036.png",
"frame": { "x": 179, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0037.png",
"frame": { "x": 85, "y": 292, "w": 82, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 4, "w": 82, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0038.png",
"frame": { "x": 252, "y": 482, "w": 81, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 81, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0039.png",
"frame": { "x": 258, "y": 290, "w": 83, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 5, "w": 83, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0040.png",
"frame": { "x": 416, "y": 484, "w": 79, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 4, "w": 79, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0041.png",
"frame": { "x": 426, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 3, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0042.png",
"frame": { "x": 445, "y": 1, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0043.png",
"frame": { "x": 179, "y": 97, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0044.png",
"frame": { "x": 1, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 2, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0045.png",
"frame": { "x": 523, "y": 195, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 2, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0046.png",
"frame": { "x": 509, "y": 292, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0047.png",
"frame": { "x": 89, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0048.png",
"frame": { "x": 531, "y": 98, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0049.png",
"frame": { "x": 258, "y": 385, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0050.png",
"frame": { "x": 267, "y": 97, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0051.png",
"frame": { "x": 1, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0052.png",
"frame": { "x": 268, "y": 1, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 87, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0053.png",
"frame": { "x": 426, "y": 389, "w": 82, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 82, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0054.png",
"frame": { "x": 1, "y": 484, "w": 81, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 6, "w": 81, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0055.png",
"frame": { "x": 341, "y": 389, "w": 83, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 7, "w": 83, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0056.png",
"frame": { "x": 84, "y": 485, "w": 79, "h": 91 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 9, "w": 79, "h": 91 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0057.png",
"frame": { "x": 167, "y": 484, "w": 81, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 7, "w": 81, "h": 92 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0058.png",
"frame": { "x": 1, "y": 195, "w": 86, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 5, "w": 86, "h": 93 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0059.png",
"frame": { "x": 265, "y": 194, "w": 86, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 3, "w": 86, "h": 94 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0060.png",
"frame": { "x": 89, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0061.png",
"frame": { "x": 1, "y": 290, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0062.png",
"frame": { "x": 1, "y": 387, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 1, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0063.png",
"frame": { "x": 353, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0064.png",
"frame": { "x": 510, "y": 389, "w": 80, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 1, "w": 80, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0065.png",
"frame": { "x": 169, "y": 387, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 1, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0066.png",
"frame": { "x": 355, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 14, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0067.png",
"frame": { "x": 443, "y": 98, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 14, "y": 2, "w": 86, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0068.png",
"frame": { "x": 90, "y": 1, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 3, "w": 87, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0069.png",
"frame": { "x": 174, "y": 290, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 5, "w": 82, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0070.png",
"frame": { "x": 84, "y": 388, "w": 81, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 4, "w": 81, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0071.png",
"frame": { "x": 438, "y": 195, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 3, "w": 83, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
},
{
"filename": "0072.png",
"frame": { "x": 335, "y": 483, "w": 79, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 2, "w": 79, "h": 95 },
"sourceSize": { "w": 100, "h": 100 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.13-x64",
"image": "815-gigantamax.png",
"format": "I8",
"size": { "w": 611, "h": 579 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,821 @@
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 525, "y": 567, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0002.png",
"frame": { "x": 441, "y": 566, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0003.png",
"frame": { "x": 609, "y": 567, "w": 83, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 83, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0004.png",
"frame": { "x": 255, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0005.png",
"frame": { "x": 171, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0006.png",
"frame": { "x": 0, "y": 569, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0007.png",
"frame": { "x": 339, "y": 569, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 1, "w": 82, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0008.png",
"frame": { "x": 0, "y": 474, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0009.png",
"frame": { "x": 464, "y": 375, "w": 89, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 1, "w": 89, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0010.png",
"frame": { "x": 580, "y": 190, "w": 92, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 1, "w": 92, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0011.png",
"frame": { "x": 95, "y": 96, "w": 94, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 1, "w": 94, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0012.png",
"frame": { "x": 198, "y": 95, "w": 95, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 95, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0013.png",
"frame": { "x": 491, "y": 0, "w": 96, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 96, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0014.png",
"frame": { "x": 297, "y": 0, "w": 98, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 98, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0015.png",
"frame": { "x": 0, "y": 0, "w": 100, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 1, "w": 100, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0016.png",
"frame": { "x": 198, "y": 0, "w": 99, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 99, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0017.png",
"frame": { "x": 100, "y": 0, "w": 98, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 0, "w": 98, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0018.png",
"frame": { "x": 395, "y": 0, "w": 96, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 0, "w": 96, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0019.png",
"frame": { "x": 189, "y": 190, "w": 92, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 0, "w": 92, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0020.png",
"frame": { "x": 89, "y": 379, "w": 88, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 0, "w": 88, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0021.png",
"frame": { "x": 525, "y": 567, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0022.png",
"frame": { "x": 441, "y": 566, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0023.png",
"frame": { "x": 609, "y": 567, "w": 83, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 83, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0024.png",
"frame": { "x": 255, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0025.png",
"frame": { "x": 171, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0026.png",
"frame": { "x": 0, "y": 569, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0027.png",
"frame": { "x": 339, "y": 569, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 1, "w": 82, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0028.png",
"frame": { "x": 0, "y": 474, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0029.png",
"frame": { "x": 464, "y": 375, "w": 89, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 1, "w": 89, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0030.png",
"frame": { "x": 580, "y": 190, "w": 92, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 1, "w": 92, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0031.png",
"frame": { "x": 95, "y": 96, "w": 94, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 1, "w": 94, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0032.png",
"frame": { "x": 198, "y": 95, "w": 95, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 95, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0033.png",
"frame": { "x": 491, "y": 0, "w": 96, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 96, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0034.png",
"frame": { "x": 297, "y": 0, "w": 98, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 98, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0035.png",
"frame": { "x": 0, "y": 0, "w": 100, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 1, "w": 100, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0036.png",
"frame": { "x": 198, "y": 0, "w": 99, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 99, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0037.png",
"frame": { "x": 100, "y": 0, "w": 98, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 0, "w": 98, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0038.png",
"frame": { "x": 395, "y": 0, "w": 96, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 0, "w": 96, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0039.png",
"frame": { "x": 189, "y": 190, "w": 92, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 0, "w": 92, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0040.png",
"frame": { "x": 89, "y": 379, "w": 88, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 0, "w": 88, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0041.png",
"frame": { "x": 525, "y": 567, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0042.png",
"frame": { "x": 441, "y": 566, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0043.png",
"frame": { "x": 609, "y": 567, "w": 83, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 83, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0044.png",
"frame": { "x": 255, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0045.png",
"frame": { "x": 171, "y": 563, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0046.png",
"frame": { "x": 0, "y": 569, "w": 83, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 11, "y": 1, "w": 83, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0047.png",
"frame": { "x": 339, "y": 569, "w": 82, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 1, "w": 82, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0048.png",
"frame": { "x": 0, "y": 474, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0049.png",
"frame": { "x": 464, "y": 375, "w": 89, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 1, "w": 89, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0050.png",
"frame": { "x": 580, "y": 190, "w": 92, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 1, "w": 92, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0051.png",
"frame": { "x": 95, "y": 96, "w": 94, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 1, "w": 94, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0052.png",
"frame": { "x": 198, "y": 95, "w": 95, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 95, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0053.png",
"frame": { "x": 491, "y": 0, "w": 96, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 96, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0054.png",
"frame": { "x": 297, "y": 0, "w": 98, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 1, "y": 1, "w": 98, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0055.png",
"frame": { "x": 0, "y": 0, "w": 100, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 1, "w": 100, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0056.png",
"frame": { "x": 198, "y": 0, "w": 99, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 1, "w": 99, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0057.png",
"frame": { "x": 100, "y": 0, "w": 98, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 2, "y": 0, "w": 98, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0058.png",
"frame": { "x": 395, "y": 0, "w": 96, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 3, "y": 0, "w": 96, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0059.png",
"frame": { "x": 189, "y": 190, "w": 92, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 0, "w": 92, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0060.png",
"frame": { "x": 89, "y": 379, "w": 88, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 0, "w": 88, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0061.png",
"frame": { "x": 525, "y": 567, "w": 84, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 0, "w": 84, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0062.png",
"frame": { "x": 454, "y": 470, "w": 86, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 10, "y": 0, "w": 86, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0063.png",
"frame": { "x": 0, "y": 379, "w": 89, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 1, "w": 89, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0064.png",
"frame": { "x": 573, "y": 285, "w": 91, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 13, "y": 1, "w": 91, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0065.png",
"frame": { "x": 93, "y": 191, "w": 92, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 15, "y": 1, "w": 92, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0066.png",
"frame": { "x": 390, "y": 96, "w": 94, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 17, "y": 1, "w": 94, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0067.png",
"frame": { "x": 585, "y": 96, "w": 94, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 17, "y": 2, "w": 94, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0068.png",
"frame": { "x": 293, "y": 95, "w": 97, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 14, "y": 3, "w": 97, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0069.png",
"frame": { "x": 484, "y": 190, "w": 96, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 12, "y": 4, "w": 96, "h": 92 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0070.png",
"frame": { "x": 92, "y": 286, "w": 92, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 3, "w": 92, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0071.png",
"frame": { "x": 177, "y": 471, "w": 89, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 4, "w": 89, "h": 92 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0072.png",
"frame": { "x": 177, "y": 379, "w": 91, "h": 92 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 4, "w": 91, "h": 92 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0073.png",
"frame": { "x": 184, "y": 286, "w": 91, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 91, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0074.png",
"frame": { "x": 480, "y": 282, "w": 93, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 93, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0075.png",
"frame": { "x": 281, "y": 281, "w": 93, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 3, "w": 93, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0076.png",
"frame": { "x": 293, "y": 188, "w": 95, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 3, "w": 95, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0077.png",
"frame": { "x": 587, "y": 0, "w": 95, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 0, "w": 95, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0078.png",
"frame": { "x": 0, "y": 95, "w": 95, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 1, "w": 95, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0079.png",
"frame": { "x": 491, "y": 95, "w": 94, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 4, "y": 1, "w": 94, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0080.png",
"frame": { "x": 0, "y": 190, "w": 93, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 5, "y": 1, "w": 93, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0081.png",
"frame": { "x": 388, "y": 191, "w": 92, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 1, "w": 92, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0082.png",
"frame": { "x": 0, "y": 285, "w": 92, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 6, "y": 2, "w": 92, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0083.png",
"frame": { "x": 275, "y": 374, "w": 90, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 2, "w": 90, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0084.png",
"frame": { "x": 374, "y": 286, "w": 90, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 2, "w": 90, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0085.png",
"frame": { "x": 365, "y": 380, "w": 89, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 2, "w": 89, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0086.png",
"frame": { "x": 553, "y": 380, "w": 89, "h": 93 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 7, "y": 3, "w": 89, "h": 93 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0087.png",
"frame": { "x": 540, "y": 473, "w": 87, "h": 94 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 2, "w": 87, "h": 94 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0088.png",
"frame": { "x": 268, "y": 468, "w": 87, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 1, "w": 87, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0089.png",
"frame": { "x": 355, "y": 474, "w": 86, "h": 95 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 8, "y": 1, "w": 86, "h": 95 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
},
{
"filename": "0090.png",
"frame": { "x": 86, "y": 475, "w": 85, "h": 96 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 9, "y": 0, "w": 85, "h": 96 },
"sourceSize": { "w": 111, "h": 96 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.13-x64",
"image": "839-gigantamax.png",
"format": "I8",
"size": { "w": 692, "h": 664 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 2.3 KiB

@ -1 +1 @@
Subproject commit 0e5c6096ba26f6b87aed1aab3fe9b0b23f6cbb7b Subproject commit 6b3f37cb351552721232f4dabefa17bddb5b9004

View File

@ -0,0 +1,98 @@
"""
Validates the contents of the variant's masterlist file and identifies
any mismatched entries for the sprite of the same key between front, back, exp, exp back, and female.
This will create a csv file that contains all of the entries with mismatches.
An empty entry means that there was not a mismatch for that version of the sprite (meaning it matches front).
"""
import sys
if sys.version_info < (3, 7):
msg = "This script requires Python 3.7+"
raise RuntimeError(msg)
import json
import os
import csv
from dataclasses import dataclass, field
from typing import Literal as L
MASTERLIST_PATH = os.path.join(
os.path.dirname(os.path.dirname(__file__)), "public", "images", "pokemon", "variant", "_masterlist.json"
)
DEFAULT_OUTPUT_PATH = "sprite-mismatches.csv"
@dataclass(order=True)
class Sprite:
key: str = field(compare=False)
front: list[int] = field(default_factory=list, compare=False)
back: list[int] = field(default_factory=list, compare=False)
female: list[int] = field(default_factory=list, compare=False)
exp: list[int] = field(default_factory=list, compare=False)
expback: list[int] = field(default_factory=list, compare=False)
sortedKey: tuple[int] | tuple[int, str] = field(init=False, repr=False, compare=True)
def as_row(self) -> tuple[str, list[int] | L[""], list[int] | L[""], list[int] | L[""], list[int] | L[""], list[int] | L[""]]:
"""return sprite information as a tuple for csv writing"""
return (self.key, self.front or "", self.back or "", self.exp or "", self.expback or "", self.female or "")
def is_mismatch(self) -> bool:
"""return True if the female, back, or exp sprites do not match the front"""
for val in [self.back, self.exp, self.expback, self.female]:
if val != [] and val != self.front:
return True
return False
def __post_init__(self):
split = self.key.split("-", maxsplit=1)
self.sortedKey = (int(split[0]), split[1]) if len(split) == 2 else (int(split[0]),)
def make_mismatch_sprite_list(path):
with open(path, "r") as f:
masterlist: dict = json.load(f)
# Go through the keys in "front" and "back" and make sure they match the masterlist
back_data: dict[str, list[int]] = masterlist.pop("back", {})
exp_data: dict[str, list[int]] = masterlist.pop("exp", {})
exp_back_data: dict[str, list[int]] = exp_data.get("back", [])
female_data: dict[str, list[int]] = masterlist.pop("female", {})
sprites: list[Sprite] = []
for key, item in masterlist.items():
sprite = Sprite(
key, front=item, back=back_data.get(key, []), exp=exp_data.get(key, []), expback=exp_back_data.get(key, []), female=female_data.get(key, [])
)
if sprite.is_mismatch():
sprites.append(sprite)
return sprites
def write_mismatch_csv(filename: str, mismatches: list[Sprite]):
with open(filename, "w", newline="") as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["key", "front", "back", "exp", "expback", "female"])
for sprite in sorted(mismatches):
writer.writerow(sprite.as_row())
if __name__ == "__main__":
import argparse
p = argparse.ArgumentParser("find_sprite_variant_mismatches", description=__doc__)
p.add_argument(
"-o",
"--output",
default=DEFAULT_OUTPUT_PATH,
help=f"The path to a file to save the output file. If not specified, will write to {DEFAULT_OUTPUT_PATH}.",
)
p.add_argument("--masterlist", default=MASTERLIST_PATH, help=f"The path to the masterlist file to validate. Defaults to {MASTERLIST_PATH}.")
args = p.parse_args()
mismatches = make_mismatch_sprite_list(args.masterlist)
write_mismatch_csv(args.output, mismatches)

View File

@ -8,13 +8,25 @@ export let loggedInUser: UserInfo | null = null;
export const clientSessionId = Utils.randomString(32); export const clientSessionId = Utils.randomString(32);
export function initLoggedInUser(): void { export function initLoggedInUser(): void {
loggedInUser = { username: "Guest", lastSessionSlot: -1, discordId: "", googleId: "", hasAdminRole: false }; loggedInUser = {
username: "Guest",
lastSessionSlot: -1,
discordId: "",
googleId: "",
hasAdminRole: false,
};
} }
export function updateUserInfo(): Promise<[boolean, number]> { export function updateUserInfo(): Promise<[boolean, number]> {
return new Promise<[boolean, number]>(resolve => { return new Promise<[boolean, number]>(resolve => {
if (bypassLogin) { if (bypassLogin) {
loggedInUser = { username: "Guest", lastSessionSlot: -1, discordId: "", googleId: "", hasAdminRole: false }; loggedInUser = {
username: "Guest",
lastSessionSlot: -1,
discordId: "",
googleId: "",
hasAdminRole: false,
};
let lastSessionSlot = -1; let lastSessionSlot = -1;
for (let s = 0; s < 5; s++) { for (let s = 0; s < 5; s++) {
if (localStorage.getItem(`sessionData${s ? s : ""}_${loggedInUser.username}`)) { if (localStorage.getItem(`sessionData${s ? s : ""}_${loggedInUser.username}`)) {
@ -24,7 +36,7 @@ export function updateUserInfo(): Promise<[boolean, number]> {
} }
loggedInUser.lastSessionSlot = lastSessionSlot; loggedInUser.lastSessionSlot = lastSessionSlot;
// Migrate old data from before the username was appended // 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); const lsItem = localStorage.getItem(d);
if (lsItem && !!loggedInUser?.username) { if (lsItem && !!loggedInUser?.username) {
const lsUserItem = localStorage.getItem(`${d}_${loggedInUser.username}`); const lsUserItem = localStorage.getItem(`${d}_${loggedInUser.username}`);
@ -35,16 +47,15 @@ export function updateUserInfo(): Promise<[boolean, number]> {
localStorage.removeItem(d); 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) { if (!accountInfo) {
resolve([ false, status ]); resolve([false, status]);
return; return;
} else {
loggedInUser = accountInfo;
resolve([ true, 200 ]);
} }
loggedInUser = accountInfo;
resolve([true, 200]);
}); });
}); });
} }

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ const pad_dualshock = {
LC_S: 13, LC_S: 13,
LC_W: 14, LC_W: 14,
LC_E: 15, LC_E: 15,
TOUCH: 17 TOUCH: 17,
}, },
icons: { icons: {
RC_S: "CROSS.png", RC_S: "CROSS.png",
@ -43,7 +43,7 @@ const pad_dualshock = {
LC_S: "DOWN.png", LC_S: "DOWN.png",
LC_W: "LEFT.png", LC_W: "LEFT.png",
LC_E: "RIGHT.png", LC_E: "RIGHT.png",
TOUCH: "TOUCH.png" TOUCH: "TOUCH.png",
}, },
settings: { settings: {
[SettingGamepad.Button_Up]: Button.UP, [SettingGamepad.Button_Up]: Button.UP,
@ -56,13 +56,13 @@ const pad_dualshock = {
[SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA, [SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
[SettingGamepad.Button_Menu]: Button.MENU, [SettingGamepad.Button_Menu]: Button.MENU,
[SettingGamepad.Button_Stats]: Button.STATS, [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_Shiny]: Button.CYCLE_SHINY,
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN, [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
[SettingGamepad.Button_Submit]: Button.SUBMIT [SettingGamepad.Button_Submit]: Button.SUBMIT,
}, },
default: { default: {
LC_N: SettingGamepad.Button_Up, LC_N: SettingGamepad.Button_Up,

View File

@ -23,7 +23,7 @@ const pad_generic = {
LC_N: 12, LC_N: 12,
LC_S: 13, LC_S: 13,
LC_W: 14, LC_W: 14,
LC_E: 15 LC_E: 15,
}, },
icons: { icons: {
RC_S: "XB_Letter_A_OL.png", RC_S: "XB_Letter_A_OL.png",
@ -54,12 +54,12 @@ const pad_generic = {
[SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA, [SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
[SettingGamepad.Button_Menu]: Button.MENU, [SettingGamepad.Button_Menu]: Button.MENU,
[SettingGamepad.Button_Stats]: Button.STATS, [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_Shiny]: Button.CYCLE_SHINY,
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
}, },
default: { default: {
LC_N: SettingGamepad.Button_Up, LC_N: SettingGamepad.Button_Up,
@ -77,14 +77,9 @@ const pad_generic = {
LT: SettingGamepad.Button_Cycle_Gender, LT: SettingGamepad.Button_Cycle_Gender,
RT: SettingGamepad.Button_Cycle_Ability, RT: SettingGamepad.Button_Cycle_Ability,
LS: SettingGamepad.Button_Speed_Up, LS: SettingGamepad.Button_Speed_Up,
RS: SettingGamepad.Button_Slow_Down RS: SettingGamepad.Button_Slow_Down,
}, },
blacklist: [ blacklist: ["LC_N", "LC_S", "LC_W", "LC_E"],
"LC_N",
"LC_S",
"LC_W",
"LC_E",
]
}; };
export default pad_generic; export default pad_generic;

View File

@ -55,12 +55,12 @@ const pad_procon = {
[SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA, [SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
[SettingGamepad.Button_Menu]: Button.MENU, [SettingGamepad.Button_Menu]: Button.MENU,
[SettingGamepad.Button_Stats]: Button.STATS, [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_Shiny]: Button.CYCLE_SHINY,
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
}, },
default: { default: {
LC_N: SettingGamepad.Button_Up, LC_N: SettingGamepad.Button_Up,
@ -78,7 +78,7 @@ const pad_procon = {
LT: SettingGamepad.Button_Cycle_Gender, LT: SettingGamepad.Button_Cycle_Gender,
RT: SettingGamepad.Button_Cycle_Ability, RT: SettingGamepad.Button_Cycle_Ability,
LS: SettingGamepad.Button_Speed_Up, LS: SettingGamepad.Button_Speed_Up,
RS: SettingGamepad.Button_Slow_Down RS: SettingGamepad.Button_Slow_Down,
}, },
}; };

View File

@ -7,7 +7,7 @@ import { Button } from "#enums/buttons";
const pad_unlicensedSNES = { const pad_unlicensedSNES = {
padID: "081f-e401", padID: "081f-e401",
padType: "xbox", padType: "xbox",
deviceMapping : { deviceMapping: {
RC_S: 2, RC_S: 2,
RC_E: 1, RC_E: 1,
RC_W: 3, RC_W: 3,
@ -19,7 +19,7 @@ const pad_unlicensedSNES = {
LC_N: 12, LC_N: 12,
LC_S: 13, LC_S: 13,
LC_W: 14, LC_W: 14,
LC_E: 15 LC_E: 15,
}, },
icons: { icons: {
RC_S: "XB_Letter_A_OL.png", RC_S: "XB_Letter_A_OL.png",
@ -46,12 +46,12 @@ const pad_unlicensedSNES = {
[SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA, [SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
[SettingGamepad.Button_Menu]: Button.MENU, [SettingGamepad.Button_Menu]: Button.MENU,
[SettingGamepad.Button_Stats]: Button.STATS, [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_Shiny]: Button.CYCLE_SHINY,
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
}, },
default: { default: {
LC_N: SettingGamepad.Button_Up, LC_N: SettingGamepad.Button_Up,
@ -69,7 +69,7 @@ const pad_unlicensedSNES = {
LT: -1, LT: -1,
RT: -1, RT: -1,
LS: -1, LS: -1,
RS: -1 RS: -1,
}, },
}; };

View File

@ -23,7 +23,7 @@ const pad_xbox360 = {
LC_N: 12, LC_N: 12,
LC_S: 13, LC_S: 13,
LC_W: 14, LC_W: 14,
LC_E: 15 LC_E: 15,
}, },
icons: { icons: {
RC_S: "XB_Letter_A_OL.png", RC_S: "XB_Letter_A_OL.png",
@ -54,12 +54,12 @@ const pad_xbox360 = {
[SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA, [SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
[SettingGamepad.Button_Menu]: Button.MENU, [SettingGamepad.Button_Menu]: Button.MENU,
[SettingGamepad.Button_Stats]: Button.STATS, [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_Shiny]: Button.CYCLE_SHINY,
[SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER, [SettingGamepad.Button_Cycle_Gender]: Button.CYCLE_GENDER,
[SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY, [SettingGamepad.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingGamepad.Button_Speed_Up]: Button.SPEED_UP, [SettingGamepad.Button_Speed_Up]: Button.SPEED_UP,
[SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN [SettingGamepad.Button_Slow_Down]: Button.SLOW_DOWN,
}, },
default: { default: {
LC_N: SettingGamepad.Button_Up, LC_N: SettingGamepad.Button_Up,
@ -77,7 +77,7 @@ const pad_xbox360 = {
LT: SettingGamepad.Button_Cycle_Gender, LT: SettingGamepad.Button_Cycle_Gender,
RT: SettingGamepad.Button_Cycle_Ability, RT: SettingGamepad.Button_Cycle_Ability,
LS: SettingGamepad.Button_Speed_Up, LS: SettingGamepad.Button_Speed_Up,
RS: SettingGamepad.Button_Slow_Down RS: SettingGamepad.Button_Slow_Down,
}, },
}; };

View File

@ -1,7 +1,7 @@
import type { EnemyPokemon, PokemonMove } from "../field/pokemon"; import type { EnemyPokemon, PokemonMove } from "../field/pokemon";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { HitResult, MoveResult, PlayerPokemon } from "../field/pokemon"; import { HitResult, MoveResult, PlayerPokemon } from "../field/pokemon";
import { Type } from "#enums/type"; import { PokemonType } from "#enums/pokemon-type";
import type { Constructor } from "#app/utils"; import type { Constructor } from "#app/utils";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
@ -10,8 +10,11 @@ import type { BattlerTag } from "./battler-tags";
import { BattlerTagLapseType, GroundedTag } from "./battler-tags"; import { BattlerTagLapseType, GroundedTag } from "./battler-tags";
import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect";
import { Gender } from "./gender"; import { Gender } from "./gender";
import type Move from "./move"; import type Move from "./moves/move";
import { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move"; import { AttackMove, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./moves/move";
import { MoveFlags } from "#enums/MoveFlags";
import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory";
import type { ArenaTrapTag, SuppressAbilitiesTag } from "./arena-tag"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "./arena-tag";
import { ArenaTagSide } from "./arena-tag"; import { ArenaTagSide } from "./arena-tag";
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "../modifier/modifier";
@ -156,7 +159,7 @@ export abstract class AbAttr {
public showAbility: boolean; public showAbility: boolean;
private extraCondition: AbAttrCondition; private extraCondition: AbAttrCondition;
constructor(showAbility: boolean = true) { constructor(showAbility = true) {
this.showAbility = showAbility; this.showAbility = showAbility;
} }
@ -413,7 +416,7 @@ export class AlliedFieldDamageReductionAbAttr extends PreDefendAbAttr {
} }
export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr { export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
constructor(moveType: Type, damageMultiplier: number) { constructor(moveType: PokemonType, damageMultiplier: number) {
super((target, user, move) => user.getMoveType(move) === moveType, damageMultiplier); super((target, user, move) => user.getMoveType(move) === moveType, damageMultiplier);
} }
} }
@ -425,10 +428,10 @@ export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultip
* @see {@linkcode getCondition} * @see {@linkcode getCondition}
*/ */
export class TypeImmunityAbAttr extends PreDefendAbAttr { export class TypeImmunityAbAttr extends PreDefendAbAttr {
private immuneType: Type | null; private immuneType: PokemonType | null;
private condition: AbAttrCondition | null; private condition: AbAttrCondition | null;
constructor(immuneType: Type | null, condition?: AbAttrCondition) { constructor(immuneType: PokemonType | null, condition?: AbAttrCondition) {
super(); super();
this.immuneType = immuneType; this.immuneType = immuneType;
@ -457,7 +460,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
return false; return false;
} }
getImmuneType(): Type | null { getImmuneType(): PokemonType | null {
return this.immuneType; return this.immuneType;
} }
@ -467,7 +470,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
} }
export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr { export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr {
constructor(immuneType: Type, condition?: AbAttrCondition) { constructor(immuneType: PokemonType, condition?: AbAttrCondition) {
super(immuneType, condition); super(immuneType, condition);
} }
@ -486,7 +489,7 @@ export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr {
} }
export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
constructor(immuneType: Type) { constructor(immuneType: PokemonType) {
super(immuneType); super(immuneType);
} }
@ -511,7 +514,7 @@ class TypeImmunityStatStageChangeAbAttr extends TypeImmunityAbAttr {
private stat: BattleStat; private stat: BattleStat;
private stages: number; private stages: number;
constructor(immuneType: Type, stat: BattleStat, stages: number, condition?: AbAttrCondition) { constructor(immuneType: PokemonType, stat: BattleStat, stages: number, condition?: AbAttrCondition) {
super(immuneType, condition); super(immuneType, condition);
this.stat = stat; this.stat = stat;
@ -536,7 +539,7 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr {
private tagType: BattlerTagType; private tagType: BattlerTagType;
private turnCount: number; private turnCount: number;
constructor(immuneType: Type, tagType: BattlerTagType, turnCount: number, condition?: AbAttrCondition) { constructor(immuneType: PokemonType, tagType: BattlerTagType, turnCount: number, condition?: AbAttrCondition) {
super(immuneType, condition); super(immuneType, condition);
this.tagType = tagType; this.tagType = tagType;
@ -772,7 +775,7 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr {
private selfTarget: boolean; private selfTarget: boolean;
private allOthers: boolean; private allOthers: boolean;
constructor(condition: PokemonDefendCondition, stat: BattleStat, stages: number, selfTarget: boolean = true, allOthers: boolean = false) { constructor(condition: PokemonDefendCondition, stat: BattleStat, stages: number, selfTarget = true, allOthers = false) {
super(true); super(true);
this.condition = condition; this.condition = condition;
@ -810,7 +813,7 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
private stages: number; private stages: number;
private selfTarget: boolean; private selfTarget: boolean;
constructor(condition: PokemonDefendCondition, hpGate: number, stats: BattleStat[], stages: number, selfTarget: boolean = true) { constructor(condition: PokemonDefendCondition, hpGate: number, stats: BattleStat[], stages: number, selfTarget = true) {
super(true); super(true);
this.condition = condition; this.condition = condition;
@ -904,7 +907,7 @@ export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
return i18next.t("abilityTriggers:postDefendTypeChange", { return i18next.t("abilityTriggers:postDefendTypeChange", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName, abilityName,
typeName: i18next.t(`pokemonInfo:Type.${Type[pokemon.getTypes(true)[0]]}`) typeName: i18next.t(`pokemonInfo:Type.${PokemonType[pokemon.getTypes(true)[0]]}`)
}); });
} }
} }
@ -963,7 +966,7 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (attacker.hasAbility(Abilities.OVERCOAT) || attacker.isOfType(Type.GRASS)) { if (attacker.hasAbility(Abilities.OVERCOAT) || attacker.isOfType(PokemonType.GRASS)) {
return false; return false;
} }
return super.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args); return super.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args);
@ -1317,7 +1320,7 @@ export class FieldMultiplyStatAbAttr extends AbAttr {
private multiplier: number; private multiplier: number;
private canStack: boolean; private canStack: boolean;
constructor(stat: Stat, multiplier: number, canStack: boolean = false) { constructor(stat: Stat, multiplier: number, canStack = false) {
super(false); super(false);
this.stat = stat; this.stat = stat;
@ -1353,7 +1356,7 @@ export class FieldMultiplyStatAbAttr extends AbAttr {
export class MoveTypeChangeAbAttr extends PreAttackAbAttr { export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
constructor( constructor(
private newType: Type, private newType: PokemonType,
private powerMultiplier: number, private powerMultiplier: number,
private condition?: PokemonAttackCondition private condition?: PokemonAttackCondition
) { ) {
@ -1378,7 +1381,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
/** Ability attribute for changing a pokemon's type before using a move */ /** Ability attribute for changing a pokemon's type before using a move */
export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
private moveType: Type; private moveType: PokemonType;
constructor() { constructor() {
super(true); super(true);
@ -1418,7 +1421,7 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
return i18next.t("abilityTriggers:pokemonTypeChange", { return i18next.t("abilityTriggers:pokemonTypeChange", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveType: i18next.t(`pokemonInfo:Type.${Type[this.moveType]}`), moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`),
}); });
} }
} }
@ -1507,7 +1510,7 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr {
private condition: PokemonAttackCondition; private condition: PokemonAttackCondition;
private powerMultiplier: number; private powerMultiplier: number;
constructor(condition: PokemonAttackCondition, powerMultiplier: number, showAbility: boolean = true) { constructor(condition: PokemonAttackCondition, powerMultiplier: number, showAbility = true) {
super(showAbility); super(showAbility);
this.condition = condition; this.condition = condition;
this.powerMultiplier = powerMultiplier; this.powerMultiplier = powerMultiplier;
@ -1525,13 +1528,13 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr {
} }
export class MoveTypePowerBoostAbAttr extends MovePowerBoostAbAttr { export class MoveTypePowerBoostAbAttr extends MovePowerBoostAbAttr {
constructor(boostedType: Type, powerMultiplier?: number) { constructor(boostedType: PokemonType, powerMultiplier?: number) {
super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5); super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5);
} }
} }
export class LowHpMoveTypePowerBoostAbAttr extends MoveTypePowerBoostAbAttr { export class LowHpMoveTypePowerBoostAbAttr extends MoveTypePowerBoostAbAttr {
constructor(boostedType: Type) { constructor(boostedType: PokemonType) {
super(boostedType); super(boostedType);
} }
@ -1552,7 +1555,7 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr {
* @param mult A function which takes the user, target, and move, and returns the power multiplier. 1 means no multiplier. * @param mult A function which takes the user, target, and move, and returns the power multiplier. 1 means no multiplier.
* @param {boolean} showAbility Whether to show the ability when it activates. * @param {boolean} showAbility Whether to show the ability when it activates.
*/ */
constructor(mult: (user: Pokemon, target: Pokemon, move: Move) => number, showAbility: boolean = true) { constructor(mult: (user: Pokemon, target: Pokemon, move: Move) => number, showAbility = true) {
super(showAbility); super(showAbility);
this.mult = mult; this.mult = mult;
} }
@ -1609,7 +1612,7 @@ export class PreAttackFieldMoveTypePowerBoostAbAttr extends FieldMovePowerBoostA
* @param boostedType - The type of move that will receive the power boost. * @param boostedType - The type of move that will receive the power boost.
* @param powerMultiplier - The multiplier to apply to the move's power, defaults to 1.5 if not provided. * @param powerMultiplier - The multiplier to apply to the move's power, defaults to 1.5 if not provided.
*/ */
constructor(boostedType: Type, powerMultiplier?: number) { constructor(boostedType: PokemonType, powerMultiplier?: number) {
super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5); super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5);
} }
} }
@ -1675,7 +1678,7 @@ export class PostAttackAbAttr extends AbAttr {
private attackCondition: PokemonAttackCondition; private attackCondition: PokemonAttackCondition;
/** The default attackCondition requires that the selected move is a damaging move */ /** The default attackCondition requires that the selected move is a damaging move */
constructor(attackCondition: PokemonAttackCondition = (user, target, move) => (move.category !== MoveCategory.STATUS), showAbility: boolean = true) { constructor(attackCondition: PokemonAttackCondition = (user, target, move) => (move.category !== MoveCategory.STATUS), showAbility = true) {
super(showAbility); super(showAbility);
this.attackCondition = attackCondition; this.attackCondition = attackCondition;
@ -2148,7 +2151,7 @@ export class PostSummonAbAttr extends AbAttr {
/** Should the ability activate when gained in battle? This will almost always be true */ /** Should the ability activate when gained in battle? This will almost always be true */
private activateOnGain: boolean; private activateOnGain: boolean;
constructor(showAbility: boolean = true, activateOnGain: boolean = true) { constructor(showAbility = true, activateOnGain = true) {
super(showAbility); super(showAbility);
this.activateOnGain = activateOnGain; this.activateOnGain = activateOnGain;
} }
@ -2331,7 +2334,7 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr {
private healRatio: number; private healRatio: number;
private showAnim: boolean; private showAnim: boolean;
constructor(healRatio: number, showAnim: boolean = false) { constructor(healRatio: number, showAnim = false) {
super(); super();
this.healRatio = healRatio || 4; this.healRatio = healRatio || 4;
@ -3293,7 +3296,7 @@ export class MultCritAbAttr extends AbAttr {
export class ConditionalCritAbAttr extends AbAttr { export class ConditionalCritAbAttr extends AbAttr {
private condition: PokemonAttackCondition; private condition: PokemonAttackCondition;
constructor(condition: PokemonAttackCondition, checkUser?: Boolean) { constructor(condition: PokemonAttackCondition, checkUser?: boolean) {
super(); super();
this.condition = condition; this.condition = condition;
@ -3397,7 +3400,7 @@ export class IgnoreContactAbAttr extends AbAttr { }
export class PreWeatherEffectAbAttr extends AbAttr { export class PreWeatherEffectAbAttr extends AbAttr {
applyPreWeatherEffect( applyPreWeatherEffect(
pokemon: Pokemon, pokemon: Pokemon,
passive: Boolean, passive: boolean,
simulated: boolean, simulated: boolean,
weather: Weather | null, weather: Weather | null,
cancelled: Utils.BooleanHolder, cancelled: Utils.BooleanHolder,
@ -3514,10 +3517,10 @@ function getAnticipationCondition(): AbAttrCondition {
+ (opponent.ivs[Stat.SPDEF] & 1) * 32) * 15 / 63); + (opponent.ivs[Stat.SPDEF] & 1) * 32) * 15 / 63);
const type = [ const type = [
Type.FIGHTING, Type.FLYING, Type.POISON, Type.GROUND, PokemonType.FIGHTING, PokemonType.FLYING, PokemonType.POISON, PokemonType.GROUND,
Type.ROCK, Type.BUG, Type.GHOST, Type.STEEL, PokemonType.ROCK, PokemonType.BUG, PokemonType.GHOST, PokemonType.STEEL,
Type.FIRE, Type.WATER, Type.GRASS, Type.ELECTRIC, PokemonType.FIRE, PokemonType.WATER, PokemonType.GRASS, PokemonType.ELECTRIC,
Type.PSYCHIC, Type.ICE, Type.DRAGON, Type.DARK ][iv_val]; PokemonType.PSYCHIC, PokemonType.ICE, PokemonType.DRAGON, PokemonType.DARK ][iv_val];
if (pokemon.getAttackTypeEffectiveness(type, opponent) >= 2) { if (pokemon.getAttackTypeEffectiveness(type, opponent) >= 2) {
return true; return true;
@ -3834,7 +3837,7 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr {
private allyTarget: boolean; private allyTarget: boolean;
private target: Pokemon; private target: Pokemon;
constructor(allyTarget: boolean = false) { constructor(allyTarget = false) {
super(true); super(true);
this.allyTarget = allyTarget; this.allyTarget = allyTarget;
} }
@ -4046,7 +4049,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {
* @returns `true` if any opponents are sleeping * @returns `true` if any opponents are sleeping
*/ */
override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
let hadEffect: boolean = false; let hadEffect = false;
for (const opp of pokemon.getOpponents()) { for (const opp of pokemon.getOpponents()) {
if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) {
if (!simulated) { if (!simulated) {
@ -4419,7 +4422,7 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr {
*/ */
applyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean { applyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean {
if (this.arenaTrapCondition(pokemon, otherPokemon)) { if (this.arenaTrapCondition(pokemon, otherPokemon)) {
if (otherPokemon.getTypes(true).includes(Type.GHOST) || (otherPokemon.getTypes(true).includes(Type.STELLAR) && otherPokemon.getTypes().includes(Type.GHOST))) { if (otherPokemon.getTypes(true).includes(PokemonType.GHOST) || (otherPokemon.getTypes(true).includes(PokemonType.STELLAR) && otherPokemon.getTypes().includes(PokemonType.GHOST))) {
trapped.value = false; trapped.value = false;
return false; return false;
} else if (otherPokemon.hasAbility(Abilities.RUN_AWAY)) { } else if (otherPokemon.hasAbility(Abilities.RUN_AWAY)) {
@ -4589,9 +4592,9 @@ export class RedirectMoveAbAttr extends AbAttr {
} }
export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr { export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr {
public type: Type; public type: PokemonType;
constructor(type: Type) { constructor(type: PokemonType) {
super(); super();
this.type = type; this.type = type;
} }
@ -4807,17 +4810,17 @@ export class NoFusionAbilityAbAttr extends AbAttr {
} }
export class IgnoreTypeImmunityAbAttr extends AbAttr { export class IgnoreTypeImmunityAbAttr extends AbAttr {
private defenderType: Type; private defenderType: PokemonType;
private allowedMoveTypes: Type[]; private allowedMoveTypes: PokemonType[];
constructor(defenderType: Type, allowedMoveTypes: Type[]) { constructor(defenderType: PokemonType, allowedMoveTypes: PokemonType[]) {
super(true); super(true);
this.defenderType = defenderType; this.defenderType = defenderType;
this.allowedMoveTypes = allowedMoveTypes; this.allowedMoveTypes = allowedMoveTypes;
} }
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.defenderType === (args[1] as Type) && this.allowedMoveTypes.includes(args[0] as Type)) { if (this.defenderType === (args[1] as PokemonType) && this.allowedMoveTypes.includes(args[0] as PokemonType)) {
cancelled.value = true; cancelled.value = true;
return true; return true;
} }
@ -4830,9 +4833,9 @@ export class IgnoreTypeImmunityAbAttr extends AbAttr {
*/ */
export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr {
private statusEffect: StatusEffect[]; private statusEffect: StatusEffect[];
private defenderType: Type[]; private defenderType: PokemonType[];
constructor(statusEffect: StatusEffect[], defenderType: Type[]) { constructor(statusEffect: StatusEffect[], defenderType: PokemonType[]) {
super(true); super(true);
this.statusEffect = statusEffect; this.statusEffect = statusEffect;
@ -4840,7 +4843,7 @@ export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr {
} }
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.statusEffect.includes(args[0] as StatusEffect) && this.defenderType.includes(args[1] as Type)) { if (this.statusEffect.includes(args[0] as StatusEffect) && this.defenderType.includes(args[1] as PokemonType)) {
cancelled.value = true; cancelled.value = true;
return true; return true;
} }
@ -5077,7 +5080,7 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr {
return false; return false;
} }
const currentTerrain = globalScene.arena.getTerrainType(); const currentTerrain = globalScene.arena.getTerrainType();
const typeChange: Type[] = this.determineTypeChange(pokemon, currentTerrain); const typeChange: PokemonType[] = this.determineTypeChange(pokemon, currentTerrain);
if (typeChange.length !== 0) { if (typeChange.length !== 0) {
if (pokemon.summonData.addedType && typeChange.includes(pokemon.summonData.addedType)) { if (pokemon.summonData.addedType && typeChange.includes(pokemon.summonData.addedType)) {
pokemon.summonData.addedType = null; pokemon.summonData.addedType = null;
@ -5094,20 +5097,20 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr {
* @param currentTerrain {@linkcode TerrainType} * @param currentTerrain {@linkcode TerrainType}
* @returns a list of type(s) * @returns a list of type(s)
*/ */
private determineTypeChange(pokemon: Pokemon, currentTerrain: TerrainType): Type[] { private determineTypeChange(pokemon: Pokemon, currentTerrain: TerrainType): PokemonType[] {
const typeChange: Type[] = []; const typeChange: PokemonType[] = [];
switch (currentTerrain) { switch (currentTerrain) {
case TerrainType.ELECTRIC: case TerrainType.ELECTRIC:
typeChange.push(Type.ELECTRIC); typeChange.push(PokemonType.ELECTRIC);
break; break;
case TerrainType.MISTY: case TerrainType.MISTY:
typeChange.push(Type.FAIRY); typeChange.push(PokemonType.FAIRY);
break; break;
case TerrainType.GRASSY: case TerrainType.GRASSY:
typeChange.push(Type.GRASS); typeChange.push(PokemonType.GRASS);
break; break;
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
typeChange.push(Type.PSYCHIC); typeChange.push(PokemonType.PSYCHIC);
break; break;
default: default:
pokemon.getTypes(false, false, true).forEach(t => { pokemon.getTypes(false, false, true).forEach(t => {
@ -5135,7 +5138,7 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr {
if (currentTerrain === TerrainType.NONE) { if (currentTerrain === TerrainType.NONE) {
return i18next.t("abilityTriggers:pokemonTypeChangeRevert", { pokemonNameWithAffix }); return i18next.t("abilityTriggers:pokemonTypeChangeRevert", { pokemonNameWithAffix });
} else { } else {
const moveType = i18next.t(`pokemonInfo:Type.${Type[this.determineTypeChange(pokemon, currentTerrain)[0]]}`); const moveType = i18next.t(`pokemonInfo:Type.${PokemonType[this.determineTypeChange(pokemon, currentTerrain)[0]]}`);
return i18next.t("abilityTriggers:pokemonTypeChange", { pokemonNameWithAffix, moveType }); return i18next.t("abilityTriggers:pokemonTypeChange", { pokemonNameWithAffix, moveType });
} }
} }
@ -5147,9 +5150,9 @@ function applySingleAbAttrs<TAttr extends AbAttr>(
attrType: Constructor<TAttr>, attrType: Constructor<TAttr>,
applyFunc: AbAttrApplyFunc<TAttr>, applyFunc: AbAttrApplyFunc<TAttr>,
args: any[], args: any[],
gainedMidTurn: boolean = false, gainedMidTurn = false,
simulated: boolean = false, simulated = false,
showAbilityInstant: boolean = false, showAbilityInstant = false,
messages: string[] = [] messages: string[] = []
) { ) {
if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) {
@ -5359,7 +5362,7 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr {
private helper: ForceSwitchOutHelper = new ForceSwitchOutHelper(SwitchType.SWITCH); private helper: ForceSwitchOutHelper = new ForceSwitchOutHelper(SwitchType.SWITCH);
private hpRatio: number; private hpRatio: number;
constructor(hpRatio: number = 0.5) { constructor(hpRatio = 0.5) {
super(); super();
this.hpRatio = hpRatio; this.hpRatio = hpRatio;
} }
@ -5443,10 +5446,10 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(
pokemon: Pokemon | null, pokemon: Pokemon | null,
applyFunc: AbAttrApplyFunc<TAttr>, applyFunc: AbAttrApplyFunc<TAttr>,
args: any[], args: any[],
showAbilityInstant: boolean = false, showAbilityInstant = false,
simulated: boolean = false, simulated = false,
messages: string[] = [], messages: string[] = [],
gainedMidTurn: boolean = false gainedMidTurn = false
) { ) {
for (const passive of [ false, true ]) { for (const passive of [ false, true ]) {
if (pokemon) { if (pokemon) {
@ -5460,7 +5463,7 @@ export function applyAbAttrs(
attrType: Constructor<AbAttr>, attrType: Constructor<AbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
cancelled: Utils.BooleanHolder | null, cancelled: Utils.BooleanHolder | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<AbAttr>( applyAbAttrsInternal<AbAttr>(
@ -5476,7 +5479,7 @@ export function applyAbAttrs(
export function applyPostBattleInitAbAttrs( export function applyPostBattleInitAbAttrs(
attrType: Constructor<PostBattleInitAbAttr>, attrType: Constructor<PostBattleInitAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostBattleInitAbAttr>( applyAbAttrsInternal<PostBattleInitAbAttr>(
@ -5495,7 +5498,7 @@ export function applyPreDefendAbAttrs(
attacker: Pokemon, attacker: Pokemon,
move: Move | null, move: Move | null,
cancelled: Utils.BooleanHolder | null, cancelled: Utils.BooleanHolder | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreDefendAbAttr>( applyAbAttrsInternal<PreDefendAbAttr>(
@ -5514,7 +5517,7 @@ export function applyPostDefendAbAttrs(
attacker: Pokemon, attacker: Pokemon,
move: Move, move: Move,
hitResult: HitResult | null, hitResult: HitResult | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostDefendAbAttr>( applyAbAttrsInternal<PostDefendAbAttr>(
@ -5533,7 +5536,7 @@ export function applyPostMoveUsedAbAttrs(
move: PokemonMove, move: PokemonMove,
source: Pokemon, source: Pokemon,
targets: BattlerIndex[], targets: BattlerIndex[],
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostMoveUsedAbAttr>( applyAbAttrsInternal<PostMoveUsedAbAttr>(
@ -5551,7 +5554,7 @@ export function applyStatMultiplierAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
stat: BattleStat, stat: BattleStat,
statValue: Utils.NumberHolder, statValue: Utils.NumberHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<StatMultiplierAbAttr>( applyAbAttrsInternal<StatMultiplierAbAttr>(
@ -5566,7 +5569,7 @@ export function applyPostSetStatusAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
effect: StatusEffect, effect: StatusEffect,
sourcePokemon?: Pokemon | null, sourcePokemon?: Pokemon | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostSetStatusAbAttr>( applyAbAttrsInternal<PostSetStatusAbAttr>(
@ -5584,7 +5587,7 @@ export function applyPostDamageAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
damage: number, damage: number,
passive: boolean, passive: boolean,
simulated: boolean = false, simulated = false,
args: any[], args: any[],
source?: Pokemon, source?: Pokemon,
): void { ): void {
@ -5613,7 +5616,7 @@ export function applyFieldStatMultiplierAbAttrs(
statValue: Utils.NumberHolder, statValue: Utils.NumberHolder,
checkedPokemon: Pokemon, checkedPokemon: Pokemon,
hasApplied: Utils.BooleanHolder, hasApplied: Utils.BooleanHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<FieldMultiplyStatAbAttr>( applyAbAttrsInternal<FieldMultiplyStatAbAttr>(
@ -5630,7 +5633,7 @@ export function applyPreAttackAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
defender: Pokemon | null, defender: Pokemon | null,
move: Move, move: Move,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreAttackAbAttr>( applyAbAttrsInternal<PreAttackAbAttr>(
@ -5649,7 +5652,7 @@ export function applyPostAttackAbAttrs(
defender: Pokemon, defender: Pokemon,
move: Move, move: Move,
hitResult: HitResult | null, hitResult: HitResult | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostAttackAbAttr>( applyAbAttrsInternal<PostAttackAbAttr>(
@ -5666,7 +5669,7 @@ export function applyPostKnockOutAbAttrs(
attrType: Constructor<PostKnockOutAbAttr>, attrType: Constructor<PostKnockOutAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
knockedOut: Pokemon, knockedOut: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostKnockOutAbAttr>( applyAbAttrsInternal<PostKnockOutAbAttr>(
@ -5682,7 +5685,7 @@ export function applyPostKnockOutAbAttrs(
export function applyPostVictoryAbAttrs( export function applyPostVictoryAbAttrs(
attrType: Constructor<PostVictoryAbAttr>, attrType: Constructor<PostVictoryAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostVictoryAbAttr>( applyAbAttrsInternal<PostVictoryAbAttr>(
@ -5698,7 +5701,7 @@ export function applyPostVictoryAbAttrs(
export function applyPostSummonAbAttrs( export function applyPostSummonAbAttrs(
attrType: Constructor<PostSummonAbAttr>, attrType: Constructor<PostSummonAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostSummonAbAttr>( applyAbAttrsInternal<PostSummonAbAttr>(
@ -5714,7 +5717,7 @@ export function applyPostSummonAbAttrs(
export function applyPreSwitchOutAbAttrs( export function applyPreSwitchOutAbAttrs(
attrType: Constructor<PreSwitchOutAbAttr>, attrType: Constructor<PreSwitchOutAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreSwitchOutAbAttr>( applyAbAttrsInternal<PreSwitchOutAbAttr>(
@ -5730,7 +5733,7 @@ export function applyPreSwitchOutAbAttrs(
export function applyPreLeaveFieldAbAttrs( export function applyPreLeaveFieldAbAttrs(
attrType: Constructor<PreLeaveFieldAbAttr>, attrType: Constructor<PreLeaveFieldAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
return applyAbAttrsInternal<PreLeaveFieldAbAttr>( return applyAbAttrsInternal<PreLeaveFieldAbAttr>(
@ -5749,7 +5752,7 @@ export function applyPreStatStageChangeAbAttrs(
pokemon: Pokemon | null, pokemon: Pokemon | null,
stat: BattleStat, stat: BattleStat,
cancelled: Utils.BooleanHolder, cancelled: Utils.BooleanHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreStatStageChangeAbAttr>( applyAbAttrsInternal<PreStatStageChangeAbAttr>(
@ -5768,7 +5771,7 @@ export function applyPostStatStageChangeAbAttrs(
stats: BattleStat[], stats: BattleStat[],
stages: integer, stages: integer,
selfTarget: boolean, selfTarget: boolean,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostStatStageChangeAbAttr>( applyAbAttrsInternal<PostStatStageChangeAbAttr>(
@ -5786,7 +5789,7 @@ export function applyPreSetStatusAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
effect: StatusEffect | undefined, effect: StatusEffect | undefined,
cancelled: Utils.BooleanHolder, cancelled: Utils.BooleanHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreSetStatusAbAttr>( applyAbAttrsInternal<PreSetStatusAbAttr>(
@ -5804,7 +5807,7 @@ export function applyPreApplyBattlerTagAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
tag: BattlerTag, tag: BattlerTag,
cancelled: Utils.BooleanHolder, cancelled: Utils.BooleanHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreApplyBattlerTagAbAttr>( applyAbAttrsInternal<PreApplyBattlerTagAbAttr>(
@ -5822,7 +5825,7 @@ export function applyPreWeatherEffectAbAttrs(
pokemon: Pokemon, pokemon: Pokemon,
weather: Weather | null, weather: Weather | null,
cancelled: Utils.BooleanHolder, cancelled: Utils.BooleanHolder,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PreWeatherDamageAbAttr>( applyAbAttrsInternal<PreWeatherDamageAbAttr>(
@ -5838,7 +5841,7 @@ export function applyPreWeatherEffectAbAttrs(
export function applyPostTurnAbAttrs( export function applyPostTurnAbAttrs(
attrType: Constructor<PostTurnAbAttr>, attrType: Constructor<PostTurnAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostTurnAbAttr>( applyAbAttrsInternal<PostTurnAbAttr>(
@ -5855,7 +5858,7 @@ export function applyPostWeatherChangeAbAttrs(
attrType: Constructor<PostWeatherChangeAbAttr>, attrType: Constructor<PostWeatherChangeAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
weather: WeatherType, weather: WeatherType,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostWeatherChangeAbAttr>( applyAbAttrsInternal<PostWeatherChangeAbAttr>(
@ -5872,7 +5875,7 @@ export function applyPostWeatherLapseAbAttrs(
attrType: Constructor<PostWeatherLapseAbAttr>, attrType: Constructor<PostWeatherLapseAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
weather: Weather | null, weather: Weather | null,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostWeatherLapseAbAttr>( applyAbAttrsInternal<PostWeatherLapseAbAttr>(
@ -5889,7 +5892,7 @@ export function applyPostTerrainChangeAbAttrs(
attrType: Constructor<PostTerrainChangeAbAttr>, attrType: Constructor<PostTerrainChangeAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
terrain: TerrainType, terrain: TerrainType,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostTerrainChangeAbAttr>( applyAbAttrsInternal<PostTerrainChangeAbAttr>(
@ -5908,7 +5911,7 @@ export function applyCheckTrappedAbAttrs(
trapped: Utils.BooleanHolder, trapped: Utils.BooleanHolder,
otherPokemon: Pokemon, otherPokemon: Pokemon,
messages: string[], messages: string[],
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<CheckTrappedAbAttr>( applyAbAttrsInternal<CheckTrappedAbAttr>(
@ -5925,7 +5928,7 @@ export function applyCheckTrappedAbAttrs(
export function applyPostBattleAbAttrs( export function applyPostBattleAbAttrs(
attrType: Constructor<PostBattleAbAttr>, attrType: Constructor<PostBattleAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostBattleAbAttr>( applyAbAttrsInternal<PostBattleAbAttr>(
@ -5944,7 +5947,7 @@ export function applyPostFaintAbAttrs(
attacker?: Pokemon, attacker?: Pokemon,
move?: Move, move?: Move,
hitResult?: HitResult, hitResult?: HitResult,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostFaintAbAttr>( applyAbAttrsInternal<PostFaintAbAttr>(
@ -5960,7 +5963,7 @@ export function applyPostFaintAbAttrs(
export function applyPostItemLostAbAttrs( export function applyPostItemLostAbAttrs(
attrType: Constructor<PostItemLostAbAttr>, attrType: Constructor<PostItemLostAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<PostItemLostAbAttr>( applyAbAttrsInternal<PostItemLostAbAttr>(
@ -5976,14 +5979,14 @@ export function applyPostItemLostAbAttrs(
* *
* Ignores passives as they don't change and shouldn't be reapplied when main abilities change * Ignores passives as they don't change and shouldn't be reapplied when main abilities change
*/ */
export function applyOnGainAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void { export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
applySingleAbAttrs<PostSummonAbAttr>(pokemon, passive, PostSummonAbAttr, (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), args, true, simulated); applySingleAbAttrs<PostSummonAbAttr>(pokemon, passive, PostSummonAbAttr, (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), args, true, simulated);
} }
/** /**
* Clears primal weather/neutralizing gas during the turn if {@linkcode pokemon}'s ability corresponds to one * Clears primal weather/neutralizing gas during the turn if {@linkcode pokemon}'s ability corresponds to one
*/ */
export function applyOnLoseAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void { export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
applySingleAbAttrs<PreLeaveFieldAbAttr>(pokemon, passive, PreLeaveFieldAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated); applySingleAbAttrs<PreLeaveFieldAbAttr>(pokemon, passive, PreLeaveFieldAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated);
} }
function queueShowAbility(pokemon: Pokemon, passive: boolean): void { function queueShowAbility(pokemon: Pokemon, passive: boolean): void {
@ -6045,10 +6048,10 @@ export function initAbilities() {
.attr(PostDefendContactApplyStatusEffectAbAttr, 30, StatusEffect.PARALYSIS) .attr(PostDefendContactApplyStatusEffectAbAttr, 30, StatusEffect.PARALYSIS)
.bypassFaint(), .bypassFaint(),
new Ability(Abilities.VOLT_ABSORB, 3) new Ability(Abilities.VOLT_ABSORB, 3)
.attr(TypeImmunityHealAbAttr, Type.ELECTRIC) .attr(TypeImmunityHealAbAttr, PokemonType.ELECTRIC)
.ignorable(), .ignorable(),
new Ability(Abilities.WATER_ABSORB, 3) new Ability(Abilities.WATER_ABSORB, 3)
.attr(TypeImmunityHealAbAttr, Type.WATER) .attr(TypeImmunityHealAbAttr, PokemonType.WATER)
.ignorable(), .ignorable(),
new Ability(Abilities.OBLIVIOUS, 3) new Ability(Abilities.OBLIVIOUS, 3)
.attr(BattlerTagImmunityAbAttr, [ BattlerTagType.INFATUATED, BattlerTagType.TAUNT ]) .attr(BattlerTagImmunityAbAttr, [ BattlerTagType.INFATUATED, BattlerTagType.TAUNT ])
@ -6073,7 +6076,7 @@ export function initAbilities() {
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.ignorable(), .ignorable(),
new Ability(Abilities.FLASH_FIRE, 3) new Ability(Abilities.FLASH_FIRE, 3)
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1) .attr(TypeImmunityAddBattlerTagAbAttr, PokemonType.FIRE, BattlerTagType.FIRE_BOOST, 1)
.ignorable(), .ignorable(),
new Ability(Abilities.SHIELD_DUST, 3) new Ability(Abilities.SHIELD_DUST, 3)
.attr(IgnoreMoveEffectsAbAttr) .attr(IgnoreMoveEffectsAbAttr)
@ -6103,7 +6106,7 @@ export function initAbilities() {
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.LEVITATE, 3) new Ability(Abilities.LEVITATE, 3)
.attr(AttackTypeImmunityAbAttr, Type.GROUND, (pokemon: Pokemon) => !pokemon.getTag(GroundedTag) && !globalScene.arena.getTag(ArenaTagType.GRAVITY)) .attr(AttackTypeImmunityAbAttr, PokemonType.GROUND, (pokemon: Pokemon) => !pokemon.getTag(GroundedTag) && !globalScene.arena.getTag(ArenaTagType.GRAVITY))
.ignorable(), .ignorable(),
new Ability(Abilities.EFFECT_SPORE, 3) new Ability(Abilities.EFFECT_SPORE, 3)
.attr(EffectSporeAbAttr), .attr(EffectSporeAbAttr),
@ -6116,8 +6119,8 @@ export function initAbilities() {
new Ability(Abilities.NATURAL_CURE, 3) new Ability(Abilities.NATURAL_CURE, 3)
.attr(PreSwitchOutResetStatusAbAttr), .attr(PreSwitchOutResetStatusAbAttr),
new Ability(Abilities.LIGHTNING_ROD, 3) new Ability(Abilities.LIGHTNING_ROD, 3)
.attr(RedirectTypeMoveAbAttr, Type.ELECTRIC) .attr(RedirectTypeMoveAbAttr, PokemonType.ELECTRIC)
.attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPATK, 1) .attr(TypeImmunityStatStageChangeAbAttr, PokemonType.ELECTRIC, Stat.SPATK, 1)
.ignorable(), .ignorable(),
new Ability(Abilities.SERENE_GRACE, 3) new Ability(Abilities.SERENE_GRACE, 3)
.attr(MoveEffectChanceMultiplierAbAttr, 2), .attr(MoveEffectChanceMultiplierAbAttr, 2),
@ -6152,7 +6155,7 @@ export function initAbilities() {
.ignorable(), .ignorable(),
new Ability(Abilities.MAGNET_PULL, 3) new Ability(Abilities.MAGNET_PULL, 3)
.attr(ArenaTrapAbAttr, (user, target) => { .attr(ArenaTrapAbAttr, (user, target) => {
if (target.getTypes(true).includes(Type.STEEL) || (target.getTypes(true).includes(Type.STELLAR) && target.getTypes().includes(Type.STEEL))) { if (target.getTypes(true).includes(PokemonType.STEEL) || (target.getTypes(true).includes(PokemonType.STELLAR) && target.getTypes().includes(PokemonType.STEEL))) {
return true; return true;
} }
return false; return false;
@ -6169,8 +6172,8 @@ export function initAbilities() {
.attr(IncreasePpAbAttr) .attr(IncreasePpAbAttr)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonPressure", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })), .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonPressure", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })),
new Ability(Abilities.THICK_FAT, 3) new Ability(Abilities.THICK_FAT, 3)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 0.5)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.ICE, 0.5) .attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.ICE, 0.5)
.ignorable(), .ignorable(),
new Ability(Abilities.EARLY_BIRD, 3) new Ability(Abilities.EARLY_BIRD, 3)
.attr(ReduceStatusEffectDurationAbAttr, StatusEffect.SLEEP), .attr(ReduceStatusEffectDurationAbAttr, StatusEffect.SLEEP),
@ -6219,13 +6222,13 @@ export function initAbilities() {
new Ability(Abilities.LIQUID_OOZE, 3) new Ability(Abilities.LIQUID_OOZE, 3)
.attr(ReverseDrainAbAttr), .attr(ReverseDrainAbAttr),
new Ability(Abilities.OVERGROW, 3) new Ability(Abilities.OVERGROW, 3)
.attr(LowHpMoveTypePowerBoostAbAttr, Type.GRASS), .attr(LowHpMoveTypePowerBoostAbAttr, PokemonType.GRASS),
new Ability(Abilities.BLAZE, 3) new Ability(Abilities.BLAZE, 3)
.attr(LowHpMoveTypePowerBoostAbAttr, Type.FIRE), .attr(LowHpMoveTypePowerBoostAbAttr, PokemonType.FIRE),
new Ability(Abilities.TORRENT, 3) new Ability(Abilities.TORRENT, 3)
.attr(LowHpMoveTypePowerBoostAbAttr, Type.WATER), .attr(LowHpMoveTypePowerBoostAbAttr, PokemonType.WATER),
new Ability(Abilities.SWARM, 3) new Ability(Abilities.SWARM, 3)
.attr(LowHpMoveTypePowerBoostAbAttr, Type.BUG), .attr(LowHpMoveTypePowerBoostAbAttr, PokemonType.BUG),
new Ability(Abilities.ROCK_HEAD, 3) new Ability(Abilities.ROCK_HEAD, 3)
.attr(BlockRecoilDamageAttr), .attr(BlockRecoilDamageAttr),
new Ability(Abilities.DROUGHT, 3) new Ability(Abilities.DROUGHT, 3)
@ -6261,7 +6264,7 @@ export function initAbilities() {
.conditionalAttr(pokemon => !!pokemon.getTag(BattlerTagType.CONFUSED), StatMultiplierAbAttr, Stat.EVA, 2) .conditionalAttr(pokemon => !!pokemon.getTag(BattlerTagType.CONFUSED), StatMultiplierAbAttr, Stat.EVA, 2)
.ignorable(), .ignorable(),
new Ability(Abilities.MOTOR_DRIVE, 4) new Ability(Abilities.MOTOR_DRIVE, 4)
.attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPD, 1) .attr(TypeImmunityStatStageChangeAbAttr, PokemonType.ELECTRIC, Stat.SPD, 1)
.ignorable(), .ignorable(),
new Ability(Abilities.RIVALRY, 4) new Ability(Abilities.RIVALRY, 4)
.attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25, true) .attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25, true)
@ -6282,7 +6285,7 @@ export function initAbilities() {
.bypassFaint() // Allows reviver seed to activate Unburden .bypassFaint() // Allows reviver seed to activate Unburden
.edgeCase(), // Should not restore Unburden boost if Pokemon loses then regains Unburden ability .edgeCase(), // Should not restore Unburden boost if Pokemon loses then regains Unburden ability
new Ability(Abilities.HEATPROOF, 4) new Ability(Abilities.HEATPROOF, 4)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 0.5)
.attr(ReduceBurnDamageAbAttr, 0.5) .attr(ReduceBurnDamageAbAttr, 0.5)
.ignorable(), .ignorable(),
new Ability(Abilities.SIMPLE, 4) new Ability(Abilities.SIMPLE, 4)
@ -6291,8 +6294,8 @@ export function initAbilities() {
new Ability(Abilities.DRY_SKIN, 4) new Ability(Abilities.DRY_SKIN, 4)
.attr(PostWeatherLapseDamageAbAttr, 2, WeatherType.SUNNY, WeatherType.HARSH_SUN) .attr(PostWeatherLapseDamageAbAttr, 2, WeatherType.SUNNY, WeatherType.HARSH_SUN)
.attr(PostWeatherLapseHealAbAttr, 2, WeatherType.RAIN, WeatherType.HEAVY_RAIN) .attr(PostWeatherLapseHealAbAttr, 2, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 1.25) .attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 1.25)
.attr(TypeImmunityHealAbAttr, Type.WATER) .attr(TypeImmunityHealAbAttr, PokemonType.WATER)
.ignorable(), .ignorable(),
new Ability(Abilities.DOWNLOAD, 4) new Ability(Abilities.DOWNLOAD, 4)
.attr(DownloadAbAttr), .attr(DownloadAbAttr),
@ -6316,7 +6319,7 @@ export function initAbilities() {
.conditionalAttr(pokemon => pokemon.status ? pokemon.status.effect === StatusEffect.PARALYSIS : false, StatMultiplierAbAttr, Stat.SPD, 2) .conditionalAttr(pokemon => pokemon.status ? pokemon.status.effect === StatusEffect.PARALYSIS : false, StatMultiplierAbAttr, Stat.SPD, 2)
.conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), StatMultiplierAbAttr, Stat.SPD, 1.5), .conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), StatMultiplierAbAttr, Stat.SPD, 1.5),
new Ability(Abilities.NORMALIZE, 4) new Ability(Abilities.NORMALIZE, 4)
.attr(MoveTypeChangeAbAttr, Type.NORMAL, 1.2, (user, target, move) => { .attr(MoveTypeChangeAbAttr, PokemonType.NORMAL, 1.2, (user, target, move) => {
return ![ Moves.HIDDEN_POWER, Moves.WEATHER_BALL, Moves.NATURAL_GIFT, Moves.JUDGMENT, Moves.TECHNO_BLAST ].includes(move.id); return ![ Moves.HIDDEN_POWER, Moves.WEATHER_BALL, Moves.NATURAL_GIFT, Moves.JUDGMENT, Moves.TECHNO_BLAST ].includes(move.id);
}), }),
new Ability(Abilities.SNIPER, 4) new Ability(Abilities.SNIPER, 4)
@ -6363,11 +6366,11 @@ export function initAbilities() {
new Ability(Abilities.SLOW_START, 4) new Ability(Abilities.SLOW_START, 4)
.attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.SLOW_START, 5), .attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.SLOW_START, 5),
new Ability(Abilities.SCRAPPY, 4) new Ability(Abilities.SCRAPPY, 4)
.attr(IgnoreTypeImmunityAbAttr, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]) .attr(IgnoreTypeImmunityAbAttr, PokemonType.GHOST, [ PokemonType.NORMAL, PokemonType.FIGHTING ])
.attr(IntimidateImmunityAbAttr), .attr(IntimidateImmunityAbAttr),
new Ability(Abilities.STORM_DRAIN, 4) new Ability(Abilities.STORM_DRAIN, 4)
.attr(RedirectTypeMoveAbAttr, Type.WATER) .attr(RedirectTypeMoveAbAttr, PokemonType.WATER)
.attr(TypeImmunityStatStageChangeAbAttr, Type.WATER, Stat.SPATK, 1) .attr(TypeImmunityStatStageChangeAbAttr, PokemonType.WATER, Stat.SPATK, 1)
.ignorable(), .ignorable(),
new Ability(Abilities.ICE_BODY, 4) new Ability(Abilities.ICE_BODY, 4)
.attr(BlockWeatherDamageAttr, WeatherType.HAIL) .attr(BlockWeatherDamageAttr, WeatherType.HAIL)
@ -6494,12 +6497,12 @@ export function initAbilities() {
new Ability(Abilities.MOXIE, 5) new Ability(Abilities.MOXIE, 5)
.attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1), .attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1),
new Ability(Abilities.JUSTIFIED, 5) new Ability(Abilities.JUSTIFIED, 5)
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.DARK && move.category !== MoveCategory.STATUS, Stat.ATK, 1), .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.DARK && move.category !== MoveCategory.STATUS, Stat.ATK, 1),
new Ability(Abilities.RATTLED, 5) new Ability(Abilities.RATTLED, 5)
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => { .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => {
const moveType = user.getMoveType(move); const moveType = user.getMoveType(move);
return move.category !== MoveCategory.STATUS return move.category !== MoveCategory.STATUS
&& (moveType === Type.DARK || moveType === Type.BUG || moveType === Type.GHOST); && (moveType === PokemonType.DARK || moveType === PokemonType.BUG || moveType === PokemonType.GHOST);
}, Stat.SPD, 1) }, Stat.SPD, 1)
.attr(PostIntimidateStatStageChangeAbAttr, [ Stat.SPD ], 1), .attr(PostIntimidateStatStageChangeAbAttr, [ Stat.SPD ], 1),
new Ability(Abilities.MAGIC_BOUNCE, 5) new Ability(Abilities.MAGIC_BOUNCE, 5)
@ -6509,14 +6512,14 @@ export function initAbilities() {
// rely on move history // rely on move history
.edgeCase(), .edgeCase(),
new Ability(Abilities.SAP_SIPPER, 5) new Ability(Abilities.SAP_SIPPER, 5)
.attr(TypeImmunityStatStageChangeAbAttr, Type.GRASS, Stat.ATK, 1) .attr(TypeImmunityStatStageChangeAbAttr, PokemonType.GRASS, Stat.ATK, 1)
.ignorable(), .ignorable(),
new Ability(Abilities.PRANKSTER, 5) new Ability(Abilities.PRANKSTER, 5)
.attr(ChangeMovePriorityAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS, 1), .attr(ChangeMovePriorityAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS, 1),
new Ability(Abilities.SAND_FORCE, 5) new Ability(Abilities.SAND_FORCE, 5)
.attr(MoveTypePowerBoostAbAttr, Type.ROCK, 1.3) .attr(MoveTypePowerBoostAbAttr, PokemonType.ROCK, 1.3)
.attr(MoveTypePowerBoostAbAttr, Type.GROUND, 1.3) .attr(MoveTypePowerBoostAbAttr, PokemonType.GROUND, 1.3)
.attr(MoveTypePowerBoostAbAttr, Type.STEEL, 1.3) .attr(MoveTypePowerBoostAbAttr, PokemonType.STEEL, 1.3)
.attr(BlockWeatherDamageAttr, WeatherType.SANDSTORM) .attr(BlockWeatherDamageAttr, WeatherType.SANDSTORM)
.condition(getWeatherCondition(WeatherType.SANDSTORM)), .condition(getWeatherCondition(WeatherType.SANDSTORM)),
new Ability(Abilities.IRON_BARBS, 5) new Ability(Abilities.IRON_BARBS, 5)
@ -6564,7 +6567,7 @@ export function initAbilities() {
new Ability(Abilities.STRONG_JAW, 6) new Ability(Abilities.STRONG_JAW, 6)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5), .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5),
new Ability(Abilities.REFRIGERATE, 6) new Ability(Abilities.REFRIGERATE, 6)
.attr(MoveTypeChangeAbAttr, Type.ICE, 1.2, (user, target, move) => move.type === Type.NORMAL && !move.hasAttr(VariableMoveTypeAttr)), .attr(MoveTypeChangeAbAttr, PokemonType.ICE, 1.2, (user, target, move) => move.type === PokemonType.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
new Ability(Abilities.SWEET_VEIL, 6) new Ability(Abilities.SWEET_VEIL, 6)
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP)
.attr(UserFieldBattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .attr(UserFieldBattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
@ -6576,7 +6579,7 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr), .attr(NoFusionAbilityAbAttr),
new Ability(Abilities.GALE_WINGS, 6) new Ability(Abilities.GALE_WINGS, 6)
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && pokemon.getMoveType(move) === Type.FLYING, 1), .attr(ChangeMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && pokemon.getMoveType(move) === PokemonType.FLYING, 1),
new Ability(Abilities.MEGA_LAUNCHER, 6) new Ability(Abilities.MEGA_LAUNCHER, 6)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.PULSE_MOVE), 1.5), .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.PULSE_MOVE), 1.5),
new Ability(Abilities.GRASS_PELT, 6) new Ability(Abilities.GRASS_PELT, 6)
@ -6587,23 +6590,23 @@ export function initAbilities() {
new Ability(Abilities.TOUGH_CLAWS, 6) new Ability(Abilities.TOUGH_CLAWS, 6)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3), .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3),
new Ability(Abilities.PIXILATE, 6) new Ability(Abilities.PIXILATE, 6)
.attr(MoveTypeChangeAbAttr, Type.FAIRY, 1.2, (user, target, move) => move.type === Type.NORMAL && !move.hasAttr(VariableMoveTypeAttr)), .attr(MoveTypeChangeAbAttr, PokemonType.FAIRY, 1.2, (user, target, move) => move.type === PokemonType.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
new Ability(Abilities.GOOEY, 6) new Ability(Abilities.GOOEY, 6)
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), Stat.SPD, -1, false), .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), Stat.SPD, -1, false),
new Ability(Abilities.AERILATE, 6) new Ability(Abilities.AERILATE, 6)
.attr(MoveTypeChangeAbAttr, Type.FLYING, 1.2, (user, target, move) => move.type === Type.NORMAL && !move.hasAttr(VariableMoveTypeAttr)), .attr(MoveTypeChangeAbAttr, PokemonType.FLYING, 1.2, (user, target, move) => move.type === PokemonType.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
new Ability(Abilities.PARENTAL_BOND, 6) new Ability(Abilities.PARENTAL_BOND, 6)
.attr(AddSecondStrikeAbAttr, 0.25), .attr(AddSecondStrikeAbAttr, 0.25),
new Ability(Abilities.DARK_AURA, 6) new Ability(Abilities.DARK_AURA, 6)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonDarkAura", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonDarkAura", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.attr(FieldMoveTypePowerBoostAbAttr, Type.DARK, 4 / 3), .attr(FieldMoveTypePowerBoostAbAttr, PokemonType.DARK, 4 / 3),
new Ability(Abilities.FAIRY_AURA, 6) new Ability(Abilities.FAIRY_AURA, 6)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonFairyAura", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonFairyAura", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.attr(FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 4 / 3), .attr(FieldMoveTypePowerBoostAbAttr, PokemonType.FAIRY, 4 / 3),
new Ability(Abilities.AURA_BREAK, 6) new Ability(Abilities.AURA_BREAK, 6)
.ignorable() .ignorable()
.conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA)), FieldMoveTypePowerBoostAbAttr, Type.DARK, 9 / 16) .conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA)), FieldMoveTypePowerBoostAbAttr, PokemonType.DARK, 9 / 16)
.conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(Abilities.FAIRY_AURA)), FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 9 / 16) .conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(Abilities.FAIRY_AURA)), FieldMoveTypePowerBoostAbAttr, PokemonType.FAIRY, 9 / 16)
.conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA) || p.hasAbility(Abilities.FAIRY_AURA)), .conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA) || p.hasAbility(Abilities.FAIRY_AURA)),
PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAuraBreak", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })), PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAuraBreak", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })),
new Ability(Abilities.PRIMORDIAL_SEA, 6) new Ability(Abilities.PRIMORDIAL_SEA, 6)
@ -6630,7 +6633,7 @@ export function initAbilities() {
.attr(PostDamageForceSwitchAbAttr) .attr(PostDamageForceSwitchAbAttr)
.edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode .edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode
new Ability(Abilities.WATER_COMPACTION, 7) new Ability(Abilities.WATER_COMPACTION, 7)
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2), .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
new Ability(Abilities.MERCILESS, 7) new Ability(Abilities.MERCILESS, 7)
.attr(ConditionalCritAbAttr, (user, target, move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON), .attr(ConditionalCritAbAttr, (user, target, move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON),
new Ability(Abilities.SHIELDS_DOWN, 7) new Ability(Abilities.SHIELDS_DOWN, 7)
@ -6648,12 +6651,12 @@ export function initAbilities() {
new Ability(Abilities.STAKEOUT, 7) new Ability(Abilities.STAKEOUT, 7)
.attr(MovePowerBoostAbAttr, (user, target, move) => !!target?.turnData.switchedInThisTurn, 2), .attr(MovePowerBoostAbAttr, (user, target, move) => !!target?.turnData.switchedInThisTurn, 2),
new Ability(Abilities.WATER_BUBBLE, 7) new Ability(Abilities.WATER_BUBBLE, 7)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 0.5)
.attr(MoveTypePowerBoostAbAttr, Type.WATER, 2) .attr(MoveTypePowerBoostAbAttr, PokemonType.WATER, 2)
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
.ignorable(), .ignorable(),
new Ability(Abilities.STEELWORKER, 7) new Ability(Abilities.STEELWORKER, 7)
.attr(MoveTypePowerBoostAbAttr, Type.STEEL), .attr(MoveTypePowerBoostAbAttr, PokemonType.STEEL),
new Ability(Abilities.BERSERK, 7) new Ability(Abilities.BERSERK, 7)
.attr(PostDefendHpGatedStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.SPATK ], 1) .attr(PostDefendHpGatedStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.SPATK ], 1)
.condition(getSheerForceHitDisableAbCondition()), .condition(getSheerForceHitDisableAbCondition()),
@ -6663,11 +6666,11 @@ export function initAbilities() {
new Ability(Abilities.LONG_REACH, 7) new Ability(Abilities.LONG_REACH, 7)
.attr(IgnoreContactAbAttr), .attr(IgnoreContactAbAttr),
new Ability(Abilities.LIQUID_VOICE, 7) new Ability(Abilities.LIQUID_VOICE, 7)
.attr(MoveTypeChangeAbAttr, Type.WATER, 1, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED)), .attr(MoveTypeChangeAbAttr, PokemonType.WATER, 1, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED)),
new Ability(Abilities.TRIAGE, 7) new Ability(Abilities.TRIAGE, 7)
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3), .attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3),
new Ability(Abilities.GALVANIZE, 7) new Ability(Abilities.GALVANIZE, 7)
.attr(MoveTypeChangeAbAttr, Type.ELECTRIC, 1.2, (user, target, move) => move.type === Type.NORMAL && !move.hasAttr(VariableMoveTypeAttr)), .attr(MoveTypeChangeAbAttr, PokemonType.ELECTRIC, 1.2, (user, target, move) => move.type === PokemonType.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
new Ability(Abilities.SURGE_SURFER, 7) new Ability(Abilities.SURGE_SURFER, 7)
.conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), StatMultiplierAbAttr, Stat.SPD, 2), .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), StatMultiplierAbAttr, Stat.SPD, 2),
new Ability(Abilities.SCHOOLING, 7) new Ability(Abilities.SCHOOLING, 7)
@ -6715,7 +6718,7 @@ export function initAbilities() {
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
.bypassFaint(), .bypassFaint(),
new Ability(Abilities.CORROSION, 7) new Ability(Abilities.CORROSION, 7)
.attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ]) .attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ PokemonType.STEEL, PokemonType.POISON ])
.edgeCase(), // Should poison itself with toxic orb. .edgeCase(), // Should poison itself with toxic orb.
new Ability(Abilities.COMATOSE, 7) new Ability(Abilities.COMATOSE, 7)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
@ -6735,7 +6738,7 @@ export function initAbilities() {
.attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL ], 1.3), .attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL ], 1.3),
new Ability(Abilities.FLUFFY, 7) new Ability(Abilities.FLUFFY, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 0.5) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 0.5)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => user.getMoveType(move) === Type.FIRE, 2) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.FIRE, 2)
.ignorable(), .ignorable(),
new Ability(Abilities.DAZZLING, 7) new Ability(Abilities.DAZZLING, 7)
.attr(FieldPriorityMoveImmunityAbAttr) .attr(FieldPriorityMoveImmunityAbAttr)
@ -6825,7 +6828,7 @@ export function initAbilities() {
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => { .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => {
const moveType = user.getMoveType(move); const moveType = user.getMoveType(move);
return move.category !== MoveCategory.STATUS return move.category !== MoveCategory.STATUS
&& (moveType === Type.FIRE || moveType === Type.WATER); && (moveType === PokemonType.FIRE || moveType === PokemonType.WATER);
}, Stat.SPD, 6), }, Stat.SPD, 6),
new Ability(Abilities.PUNK_ROCK, 8) new Ability(Abilities.PUNK_ROCK, 8)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED), 1.3) .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED), 1.3)
@ -6864,7 +6867,7 @@ export function initAbilities() {
new Ability(Abilities.SCREEN_CLEANER, 8) new Ability(Abilities.SCREEN_CLEANER, 8)
.attr(PostSummonRemoveArenaTagAbAttr, [ ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.REFLECT ]), .attr(PostSummonRemoveArenaTagAbAttr, [ ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.REFLECT ]),
new Ability(Abilities.STEELY_SPIRIT, 8) new Ability(Abilities.STEELY_SPIRIT, 8)
.attr(UserFieldMoveTypePowerBoostAbAttr, Type.STEEL), .attr(UserFieldMoveTypePowerBoostAbAttr, PokemonType.STEEL),
new Ability(Abilities.PERISH_BODY, 8) new Ability(Abilities.PERISH_BODY, 8)
.attr(PostDefendPerishSongAbAttr, 4) .attr(PostDefendPerishSongAbAttr, 4)
.bypassFaint(), .bypassFaint(),
@ -6900,9 +6903,9 @@ export function initAbilities() {
new Ability(Abilities.CURIOUS_MEDICINE, 8) new Ability(Abilities.CURIOUS_MEDICINE, 8)
.attr(PostSummonClearAllyStatStagesAbAttr), .attr(PostSummonClearAllyStatStagesAbAttr),
new Ability(Abilities.TRANSISTOR, 8) new Ability(Abilities.TRANSISTOR, 8)
.attr(MoveTypePowerBoostAbAttr, Type.ELECTRIC), .attr(MoveTypePowerBoostAbAttr, PokemonType.ELECTRIC),
new Ability(Abilities.DRAGONS_MAW, 8) new Ability(Abilities.DRAGONS_MAW, 8)
.attr(MoveTypePowerBoostAbAttr, Type.DRAGON), .attr(MoveTypePowerBoostAbAttr, PokemonType.DRAGON),
new Ability(Abilities.CHILLING_NEIGH, 8) new Ability(Abilities.CHILLING_NEIGH, 8)
.attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1), .attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1),
new Ability(Abilities.GRIM_NEIGH, 8) new Ability(Abilities.GRIM_NEIGH, 8)
@ -6928,7 +6931,7 @@ export function initAbilities() {
.attr(PostDefendTerrainChangeAbAttr, TerrainType.GRASSY) .attr(PostDefendTerrainChangeAbAttr, TerrainType.GRASSY)
.bypassFaint(), .bypassFaint(),
new Ability(Abilities.THERMAL_EXCHANGE, 9) new Ability(Abilities.THERMAL_EXCHANGE, 9)
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1) .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
.ignorable(), .ignorable(),
new Ability(Abilities.ANGER_SHELL, 9) new Ability(Abilities.ANGER_SHELL, 9)
@ -6937,10 +6940,10 @@ export function initAbilities() {
.condition(getSheerForceHitDisableAbCondition()), .condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.PURIFYING_SALT, 9) new Ability(Abilities.PURIFYING_SALT, 9)
.attr(StatusEffectImmunityAbAttr) .attr(StatusEffectImmunityAbAttr)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.GHOST, 0.5) .attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.GHOST, 0.5)
.ignorable(), .ignorable(),
new Ability(Abilities.WELL_BAKED_BODY, 9) new Ability(Abilities.WELL_BAKED_BODY, 9)
.attr(TypeImmunityStatStageChangeAbAttr, Type.FIRE, Stat.DEF, 2) .attr(TypeImmunityStatStageChangeAbAttr, PokemonType.FIRE, Stat.DEF, 2)
.ignorable(), .ignorable(),
new Ability(Abilities.WIND_RIDER, 9) new Ability(Abilities.WIND_RIDER, 9)
.attr(MoveImmunityStatStageChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.WIND_MOVE) && move.category !== MoveCategory.STATUS, Stat.ATK, 1) .attr(MoveImmunityStatStageChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.WIND_MOVE) && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
@ -6951,7 +6954,7 @@ export function initAbilities() {
.attr(ForceSwitchOutImmunityAbAttr) .attr(ForceSwitchOutImmunityAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.ROCKY_PAYLOAD, 9) new Ability(Abilities.ROCKY_PAYLOAD, 9)
.attr(MoveTypePowerBoostAbAttr, Type.ROCK), .attr(MoveTypePowerBoostAbAttr, PokemonType.ROCK),
new Ability(Abilities.WIND_POWER, 9) new Ability(Abilities.WIND_POWER, 9)
.attr(PostDefendApplyBattlerTagAbAttr, (target, user, move) => move.hasFlag(MoveFlags.WIND_MOVE), BattlerTagType.CHARGED), .attr(PostDefendApplyBattlerTagAbAttr, (target, user, move) => move.hasFlag(MoveFlags.WIND_MOVE), BattlerTagType.CHARGED),
new Ability(Abilities.ZERO_TO_HERO, 9) new Ability(Abilities.ZERO_TO_HERO, 9)
@ -7026,14 +7029,14 @@ export function initAbilities() {
.attr(FieldPriorityMoveImmunityAbAttr) .attr(FieldPriorityMoveImmunityAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.EARTH_EATER, 9) new Ability(Abilities.EARTH_EATER, 9)
.attr(TypeImmunityHealAbAttr, Type.GROUND) .attr(TypeImmunityHealAbAttr, PokemonType.GROUND)
.ignorable(), .ignorable(),
new Ability(Abilities.MYCELIUM_MIGHT, 9) new Ability(Abilities.MYCELIUM_MIGHT, 9)
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS, -0.2) .attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS, -0.2)
.attr(PreventBypassSpeedChanceAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS) .attr(PreventBypassSpeedChanceAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS)
.attr(MoveAbilityBypassAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS), .attr(MoveAbilityBypassAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS),
new Ability(Abilities.MINDS_EYE, 9) new Ability(Abilities.MINDS_EYE, 9)
.attr(IgnoreTypeImmunityAbAttr, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]) .attr(IgnoreTypeImmunityAbAttr, PokemonType.GHOST, [ PokemonType.NORMAL, PokemonType.FIGHTING ])
.attr(ProtectStatAbAttr, Stat.ACC) .attr(ProtectStatAbAttr, Stat.ACC)
.attr(IgnoreOpponentStatStagesAbAttr, [ Stat.EVA ]) .attr(IgnoreOpponentStatStagesAbAttr, [ Stat.EVA ])
.ignorable(), .ignorable(),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -11,7 +11,7 @@ import type { FixedBattleConfig } from "#app/battle";
import { ClassicFixedBossWaves, BattleType, getRandomTrainerFunc } from "#app/battle"; import { ClassicFixedBossWaves, BattleType, getRandomTrainerFunc } from "#app/battle";
import Trainer, { TrainerVariant } from "#app/field/trainer"; import Trainer, { TrainerVariant } from "#app/field/trainer";
import type { GameMode } from "#app/game-mode"; import type { GameMode } from "#app/game-mode";
import { Type } from "#enums/type"; import { PokemonType } from "#enums/pokemon-type";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
@ -33,37 +33,37 @@ export enum ChallengeType {
/** /**
* Challenges which modify what starters you can choose * Challenges which modify what starters you can choose
* @see {@link Challenge.applyStarterChoice} * @see {@link Challenge.applyStarterChoice}
*/ */
STARTER_CHOICE, STARTER_CHOICE,
/** /**
* Challenges which modify how many starter points you have * Challenges which modify how many starter points you have
* @see {@link Challenge.applyStarterPoints} * @see {@link Challenge.applyStarterPoints}
*/ */
STARTER_POINTS, STARTER_POINTS,
/** /**
* Challenges which modify how many starter points you have * Challenges which modify how many starter points you have
* @see {@link Challenge.applyStarterPointCost} * @see {@link Challenge.applyStarterPointCost}
*/ */
STARTER_COST, STARTER_COST,
/** /**
* Challenges which modify your starters in some way * Challenges which modify your starters in some way
* @see {@link Challenge.applyStarterModify} * @see {@link Challenge.applyStarterModify}
*/ */
STARTER_MODIFY, STARTER_MODIFY,
/** /**
* Challenges which limit which pokemon you can have in battle. * Challenges which limit which pokemon you can have in battle.
* @see {@link Challenge.applyPokemonInBattle} * @see {@link Challenge.applyPokemonInBattle}
*/ */
POKEMON_IN_BATTLE, POKEMON_IN_BATTLE,
/** /**
* Adds or modifies the fixed battles in a run * Adds or modifies the fixed battles in a run
* @see {@link Challenge.applyFixedBattle} * @see {@link Challenge.applyFixedBattle}
*/ */
FIXED_BATTLES, FIXED_BATTLES,
/** /**
* Modifies the effectiveness of Type matchups in battle * Modifies the effectiveness of Type matchups in battle
* @see {@linkcode Challenge.applyTypeEffectiveness} * @see {@linkcode Challenge.applyTypeEffectiveness}
*/ */
TYPE_EFFECTIVENESS, TYPE_EFFECTIVENESS,
/** /**
* Modifies what level the AI pokemon are. UNIMPLEMENTED. * Modifies what level the AI pokemon are. UNIMPLEMENTED.
@ -93,7 +93,6 @@ export enum ChallengeType {
* Modifies what the pokemon stats for Flip Stat Mode. * Modifies what the pokemon stats for Flip Stat Mode.
*/ */
FLIP_STAT, FLIP_STAT,
} }
/** /**
@ -106,7 +105,7 @@ export enum MoveSourceType {
GREAT_TM, GREAT_TM,
ULTRA_TM, ULTRA_TM,
COMMON_EGG, COMMON_EGG,
RARE_EGG RARE_EGG,
} }
/** /**
@ -148,7 +147,10 @@ export abstract class Challenge {
* @returns {@link string} The i18n key for this challenge * @returns {@link string} The i18n key for this challenge
*/ */
geti18nKey(): string { geti18nKey(): string {
return Challenges[this.id].split("_").map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); return Challenges[this.id]
.split("_")
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("");
} }
/** /**
@ -195,7 +197,7 @@ export abstract class Challenge {
*/ */
getDescription(overrideValue?: number): string { getDescription(overrideValue?: number): string {
const value = overrideValue ?? this.value; 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. * @param source The source challenge or json.
* @returns This challenge. * @returns This challenge.
*/ */
static loadChallenge(source: Challenge | any): Challenge { static loadChallenge(_source: Challenge | any): Challenge {
throw new Error("Method not implemented! Use derived class"); throw new Error("Method not implemented! Use derived class");
} }
/** /**
* An apply function for STARTER_CHOICE challenges. Derived classes should alter this. * An apply function for STARTER_CHOICE challenges. Derived classes should alter this.
* @param pokemon {@link PokemonSpecies} The pokemon to check the validity of. * @param _pokemon {@link PokemonSpecies} The pokemon to check the validity of.
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. * @param _valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon. * @param _dexAttr {@link DexAttrProps} The dex attributes of the pokemon.
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon. * @param _soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean { applyStarterChoice(
_pokemon: PokemonSpecies,
_valid: Utils.BooleanHolder,
_dexAttr: DexAttrProps,
_soft = false,
): boolean {
return false; return false;
} }
/** /**
* An apply function for STARTER_POINTS challenges. Derived classes should alter this. * An apply function for STARTER_POINTS challenges. Derived classes should alter this.
* @param points {@link Utils.NumberHolder} The amount of points you have available. * @param _points {@link Utils.NumberHolder} The amount of points you have available.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyStarterPoints(points: Utils.NumberHolder): boolean { applyStarterPoints(_points: Utils.NumberHolder): boolean {
return false; return false;
} }
/** /**
* An apply function for STARTER_COST challenges. Derived classes should alter this. * An apply function for STARTER_COST challenges. Derived classes should alter this.
* @param species {@link Species} The pokemon to change the cost of. * @param _species {@link Species} The pokemon to change the cost of.
* @param cost {@link Utils.NumberHolder} The cost of the starter. * @param _cost {@link Utils.NumberHolder} The cost of the starter.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean { applyStarterCost(_species: Species, _cost: Utils.NumberHolder): boolean {
return false; return false;
} }
/** /**
* An apply function for STARTER_MODIFY challenges. Derived classes should alter this. * An apply function for STARTER_MODIFY challenges. Derived classes should alter this.
* @param pokemon {@link Pokemon} The starter pokemon to modify. * @param _pokemon {@link Pokemon} The starter pokemon to modify.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyStarterModify(pokemon: Pokemon): boolean { applyStarterModify(_pokemon: Pokemon): boolean {
return false; return false;
} }
/** /**
* An apply function for POKEMON_IN_BATTLE challenges. Derived classes should alter this. * An apply function for POKEMON_IN_BATTLE challenges. Derived classes should alter this.
* @param pokemon {@link Pokemon} The pokemon to check the validity of. * @param _pokemon {@link Pokemon} The pokemon to check the validity of.
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. * @param _valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { applyPokemonInBattle(_pokemon: Pokemon, _valid: Utils.BooleanHolder): boolean {
return false; return false;
} }
/** /**
* An apply function for FIXED_BATTLE challenges. Derived classes should alter this. * An apply function for FIXED_BATTLE challenges. Derived classes should alter this.
* @param waveIndex {@link Number} The current wave index. * @param _waveIndex {@link Number} The current wave index.
* @param battleConfig {@link FixedBattleConfig} The battle config to modify. * @param _battleConfig {@link FixedBattleConfig} The battle config to modify.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean { applyFixedBattle(_waveIndex: number, _battleConfig: FixedBattleConfig): boolean {
return false; return false;
} }
/** /**
* An apply function for TYPE_EFFECTIVENESS challenges. Derived classes should alter this. * An apply function for TYPE_EFFECTIVENESS challenges. Derived classes should alter this.
* @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move. * @param _effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move.
* @returns Whether this function did anything. * @returns Whether this function did anything.
*/ */
applyTypeEffectiveness(effectiveness: Utils.NumberHolder): boolean { applyTypeEffectiveness(_effectiveness: Utils.NumberHolder): boolean {
return false; return false;
} }
/** /**
* An apply function for AI_LEVEL challenges. Derived classes should alter this. * An apply function for AI_LEVEL challenges. Derived classes should alter this.
* @param level {@link Utils.NumberHolder} The generated level. * @param _level {@link Utils.NumberHolder} The generated level.
* @param levelCap {@link Number} The current level cap. * @param _levelCap {@link Number} The current level cap.
* @param isTrainer {@link Boolean} Whether this is a trainer pokemon. * @param _isTrainer {@link Boolean} Whether this is a trainer pokemon.
* @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon. * @param _isBoss {@link Boolean} Whether this is a non-trainer boss pokemon.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyLevelChange(level: Utils.NumberHolder, levelCap: number, isTrainer: boolean, isBoss: boolean): boolean { applyLevelChange(_level: Utils.NumberHolder, _levelCap: number, _isTrainer: boolean, _isBoss: boolean): boolean {
return false; return false;
} }
@ -365,7 +372,7 @@ export abstract class Challenge {
* @param moveSlots {@link Utils.NumberHolder} The amount of move slots. * @param moveSlots {@link Utils.NumberHolder} The amount of move slots.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyMoveSlot(pokemon: Pokemon, moveSlots: Utils.NumberHolder): boolean { applyMoveSlot(_pokemon: Pokemon, _moveSlots: Utils.NumberHolder): boolean {
return false; return false;
} }
@ -375,7 +382,7 @@ export abstract class Challenge {
* @param hasPassive {@link Utils.BooleanHolder} Whether it should have its passive. * @param hasPassive {@link Utils.BooleanHolder} Whether it should have its passive.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyPassiveAccess(pokemon: Pokemon, hasPassive: Utils.BooleanHolder): boolean { applyPassiveAccess(_pokemon: Pokemon, _hasPassive: Utils.BooleanHolder): boolean {
return false; return false;
} }
@ -384,41 +391,46 @@ export abstract class Challenge {
* @param gameMode {@link GameMode} The current game mode. * @param gameMode {@link GameMode} The current game mode.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyGameModeModify(gameMode: GameMode): boolean { applyGameModeModify(_gameMode: GameMode): boolean {
return false; return false;
} }
/** /**
* An apply function for MOVE_ACCESS. Derived classes should alter this. * An apply function for MOVE_ACCESS. Derived classes should alter this.
* @param pokemon {@link Pokemon} What pokemon would learn the move. * @param _pokemon {@link Pokemon} What pokemon would learn the move.
* @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. * @param _moveSource {@link MoveSourceType} What source the pokemon would get the move from.
* @param move {@link Moves} The move in question. * @param _move {@link Moves} The move in question.
* @param level {@link Utils.NumberHolder} The level threshold for access. * @param _level {@link Utils.NumberHolder} The level threshold for access.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyMoveAccessLevel(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.NumberHolder): boolean { applyMoveAccessLevel(
_pokemon: Pokemon,
_moveSource: MoveSourceType,
_move: Moves,
_level: Utils.NumberHolder,
): boolean {
return false; return false;
} }
/** /**
* An apply function for MOVE_WEIGHT. Derived classes should alter this. * An apply function for MOVE_WEIGHT. Derived classes should alter this.
* @param pokemon {@link Pokemon} What pokemon would learn the move. * @param _pokemon {@link Pokemon} What pokemon would learn the move.
* @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. * @param _moveSource {@link MoveSourceType} What source the pokemon would get the move from.
* @param move {@link Moves} The move in question. * @param _move {@link Moves} The move in question.
* @param weight {@link Utils.NumberHolder} The base weight of the move * @param _weight {@link Utils.NumberHolder} The base weight of the move
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyMoveWeight(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.NumberHolder): boolean { applyMoveWeight(_pokemon: Pokemon, _moveSource: MoveSourceType, _move: Moves, _level: Utils.NumberHolder): boolean {
return false; return false;
} }
/** /**
* An apply function for FlipStats. Derived classes should alter this. * An apply function for FlipStats. Derived classes should alter this.
* @param pokemon {@link Pokemon} What pokemon would learn the move. * @param _pokemon {@link Pokemon} What pokemon would learn the move.
* @param baseStats What are the stats to flip. * @param _baseStats What are the stats to flip.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyFlipStat(pokemon: Pokemon, baseStats: number[]) { applyFlipStat(_pokemon: Pokemon, _baseStats: number[]) {
return false; return false;
} }
} }
@ -433,10 +445,15 @@ export class SingleGenerationChallenge extends Challenge {
super(Challenges.SINGLE_GENERATION, 9); super(Challenges.SINGLE_GENERATION, 9);
} }
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean { applyStarterChoice(
const generations = [ pokemon.generation ]; pokemon: PokemonSpecies,
valid: Utils.BooleanHolder,
_dexAttr: DexAttrProps,
soft = false,
): boolean {
const generations = [pokemon.generation];
if (soft) { if (soft) {
const speciesToCheck = [ pokemon.speciesId ]; const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) { while (speciesToCheck.length) {
const checking = speciesToCheck.pop(); const checking = speciesToCheck.pop();
if (checking && pokemonEvolutions.hasOwnProperty(checking)) { if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
@ -458,7 +475,10 @@ export class SingleGenerationChallenge extends Challenge {
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
const baseGeneration = getPokemonSpecies(pokemon.species.speciesId).generation; const baseGeneration = getPokemonSpecies(pokemon.species.speciesId).generation;
const fusionGeneration = pokemon.isFusion() ? getPokemonSpecies(pokemon.fusionSpecies!.speciesId).generation : 0; // TODO: is the bang on fusionSpecies correct? const fusionGeneration = pokemon.isFusion() ? getPokemonSpecies(pokemon.fusionSpecies!.speciesId).generation : 0; // TODO: is the bang on fusionSpecies correct?
if (pokemon.isPlayer() && (baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))) { if (
pokemon.isPlayer() &&
(baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))
) {
valid.value = false; valid.value = false;
return true; return true;
} }
@ -467,11 +487,63 @@ export class SingleGenerationChallenge extends Challenge {
applyFixedBattle(waveIndex: number, battleConfig: FixedBattleConfig): boolean { applyFixedBattle(waveIndex: number, battleConfig: FixedBattleConfig): boolean {
let trainerTypes: (TrainerType | TrainerType[])[] = []; let trainerTypes: (TrainerType | TrainerType[])[] = [];
const evilTeamWaves: number[] = [ ClassicFixedBossWaves.EVIL_GRUNT_1, ClassicFixedBossWaves.EVIL_GRUNT_2, ClassicFixedBossWaves.EVIL_GRUNT_3, ClassicFixedBossWaves.EVIL_ADMIN_1, ClassicFixedBossWaves.EVIL_GRUNT_4, ClassicFixedBossWaves.EVIL_ADMIN_2, ClassicFixedBossWaves.EVIL_BOSS_1, ClassicFixedBossWaves.EVIL_BOSS_2 ]; const evilTeamWaves: number[] = [
const evilTeamGrunts = [[ TrainerType.ROCKET_GRUNT ], [ TrainerType.ROCKET_GRUNT ], [ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ], [ TrainerType.GALACTIC_GRUNT ], [ TrainerType.PLASMA_GRUNT ], [ TrainerType.FLARE_GRUNT ], [ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ], [ TrainerType.MACRO_GRUNT ], [ TrainerType.STAR_GRUNT ]]; ClassicFixedBossWaves.EVIL_GRUNT_1,
const evilTeamAdmins = [[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [[ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ]], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.COLRESS ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], [ TrainerType.FABA, TrainerType.PLUMERIA ], [ TrainerType.OLEANA ], [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]]; ClassicFixedBossWaves.EVIL_GRUNT_2,
const evilTeamBosses = [[ TrainerType.ROCKET_BOSS_GIOVANNI_1 ], [ TrainerType.ROCKET_BOSS_GIOVANNI_1 ], [ TrainerType.MAXIE, TrainerType.ARCHIE ], [ TrainerType.CYRUS ], [ TrainerType.GHETSIS ], [ TrainerType.LYSANDRE ], [ TrainerType.LUSAMINE, TrainerType.GUZMA ], [ TrainerType.ROSE ], [ TrainerType.PENNY ]]; ClassicFixedBossWaves.EVIL_GRUNT_3,
const evilTeamBossRematches = [[ TrainerType.ROCKET_BOSS_GIOVANNI_2 ], [ TrainerType.ROCKET_BOSS_GIOVANNI_2 ], [ TrainerType.MAXIE_2, TrainerType.ARCHIE_2 ], [ TrainerType.CYRUS_2 ], [ TrainerType.GHETSIS_2 ], [ TrainerType.LYSANDRE_2 ], [ TrainerType.LUSAMINE_2, TrainerType.GUZMA_2 ], [ TrainerType.ROSE_2 ], [ TrainerType.PENNY_2 ]]; ClassicFixedBossWaves.EVIL_ADMIN_1,
ClassicFixedBossWaves.EVIL_GRUNT_4,
ClassicFixedBossWaves.EVIL_ADMIN_2,
ClassicFixedBossWaves.EVIL_BOSS_1,
ClassicFixedBossWaves.EVIL_BOSS_2,
];
const evilTeamGrunts = [
[TrainerType.ROCKET_GRUNT],
[TrainerType.ROCKET_GRUNT],
[TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT],
[TrainerType.GALACTIC_GRUNT],
[TrainerType.PLASMA_GRUNT],
[TrainerType.FLARE_GRUNT],
[TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT],
[TrainerType.MACRO_GRUNT],
[TrainerType.STAR_GRUNT],
];
const evilTeamAdmins = [
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
[TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL],
[
[TrainerType.TABITHA, TrainerType.COURTNEY],
[TrainerType.MATT, TrainerType.SHELLY],
],
[TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN],
[TrainerType.ZINZOLIN, TrainerType.COLRESS],
[TrainerType.XEROSIC, TrainerType.BRYONY],
[TrainerType.FABA, TrainerType.PLUMERIA],
[TrainerType.OLEANA],
[TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI],
];
const evilTeamBosses = [
[TrainerType.ROCKET_BOSS_GIOVANNI_1],
[TrainerType.ROCKET_BOSS_GIOVANNI_1],
[TrainerType.MAXIE, TrainerType.ARCHIE],
[TrainerType.CYRUS],
[TrainerType.GHETSIS],
[TrainerType.LYSANDRE],
[TrainerType.LUSAMINE, TrainerType.GUZMA],
[TrainerType.ROSE],
[TrainerType.PENNY],
];
const evilTeamBossRematches = [
[TrainerType.ROCKET_BOSS_GIOVANNI_2],
[TrainerType.ROCKET_BOSS_GIOVANNI_2],
[TrainerType.MAXIE_2, TrainerType.ARCHIE_2],
[TrainerType.CYRUS_2],
[TrainerType.GHETSIS_2],
[TrainerType.LYSANDRE_2],
[TrainerType.LUSAMINE_2, TrainerType.GUZMA_2],
[TrainerType.ROSE_2],
[TrainerType.PENNY_2],
];
switch (waveIndex) { switch (waveIndex) {
case ClassicFixedBossWaves.EVIL_GRUNT_1: case ClassicFixedBossWaves.EVIL_GRUNT_1:
trainerTypes = evilTeamGrunts[this.value - 1]; trainerTypes = evilTeamGrunts[this.value - 1];
@ -488,42 +560,123 @@ export class SingleGenerationChallenge extends Challenge {
break; break;
case ClassicFixedBossWaves.EVIL_BOSS_1: case ClassicFixedBossWaves.EVIL_BOSS_1:
trainerTypes = evilTeamBosses[this.value - 1]; trainerTypes = evilTeamBosses[this.value - 1];
battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) battleConfig
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }); .setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
allowLuckUpgrades: false,
});
return true; return true;
case ClassicFixedBossWaves.EVIL_BOSS_2: case ClassicFixedBossWaves.EVIL_BOSS_2:
trainerTypes = evilTeamBossRematches[this.value - 1]; trainerTypes = evilTeamBossRematches[this.value - 1];
battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) battleConfig
.setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }); .setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true))
.setCustomModifierRewards({
guaranteedModifierTiers: [
ModifierTier.ROGUE,
ModifierTier.ROGUE,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
ModifierTier.ULTRA,
],
allowLuckUpgrades: false,
});
return true; return true;
case ClassicFixedBossWaves.ELITE_FOUR_1: case ClassicFixedBossWaves.ELITE_FOUR_1:
trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; trainerTypes = [
TrainerType.LORELEI,
TrainerType.WILL,
TrainerType.SIDNEY,
TrainerType.AARON,
TrainerType.SHAUNTAL,
TrainerType.MALVA,
Utils.randSeedItem([TrainerType.HALA, TrainerType.MOLAYNE]),
TrainerType.MARNIE_ELITE,
TrainerType.RIKA,
];
break; break;
case ClassicFixedBossWaves.ELITE_FOUR_2: case ClassicFixedBossWaves.ELITE_FOUR_2:
trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ]; trainerTypes = [
TrainerType.BRUNO,
TrainerType.KOGA,
TrainerType.PHOEBE,
TrainerType.BERTHA,
TrainerType.MARSHAL,
TrainerType.SIEBOLD,
TrainerType.OLIVIA,
TrainerType.NESSA_ELITE,
TrainerType.POPPY,
];
break; break;
case ClassicFixedBossWaves.ELITE_FOUR_3: case ClassicFixedBossWaves.ELITE_FOUR_3:
trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ]; trainerTypes = [
TrainerType.AGATHA,
TrainerType.BRUNO,
TrainerType.GLACIA,
TrainerType.FLINT,
TrainerType.GRIMSLEY,
TrainerType.WIKSTROM,
TrainerType.ACEROLA,
Utils.randSeedItem([TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE]),
TrainerType.LARRY_ELITE,
];
break; break;
case ClassicFixedBossWaves.ELITE_FOUR_4: case ClassicFixedBossWaves.ELITE_FOUR_4:
trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ]; trainerTypes = [
TrainerType.LANCE,
TrainerType.KAREN,
TrainerType.DRAKE,
TrainerType.LUCIAN,
TrainerType.CAITLIN,
TrainerType.DRASNA,
TrainerType.KAHILI,
TrainerType.RAIHAN_ELITE,
TrainerType.HASSEL,
];
break; break;
case ClassicFixedBossWaves.CHAMPION: case ClassicFixedBossWaves.CHAMPION:
trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, Utils.randSeedItem([ TrainerType.KUKUI, TrainerType.HAU ]), Utils.randSeedItem([ TrainerType.LEON, TrainerType.MUSTARD ]), Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; trainerTypes = [
TrainerType.BLUE,
Utils.randSeedItem([TrainerType.RED, TrainerType.LANCE_CHAMPION]),
Utils.randSeedItem([TrainerType.STEVEN, TrainerType.WALLACE]),
TrainerType.CYNTHIA,
Utils.randSeedItem([TrainerType.ALDER, TrainerType.IRIS]),
TrainerType.DIANTHA,
Utils.randSeedItem([TrainerType.KUKUI, TrainerType.HAU]),
Utils.randSeedItem([TrainerType.LEON, TrainerType.MUSTARD]),
Utils.randSeedItem([TrainerType.GEETA, TrainerType.NEMONA]),
];
break; break;
} }
if (trainerTypes.length === 0) { if (trainerTypes.length === 0) {
return false; return false;
} else if (evilTeamWaves.includes(waveIndex)) {
battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true));
return true;
} else if (waveIndex >= ClassicFixedBossWaves.ELITE_FOUR_1 && waveIndex <= ClassicFixedBossWaves.CHAMPION) {
const ttypes = trainerTypes as TrainerType[];
battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(() => new Trainer(ttypes[this.value - 1], TrainerVariant.DEFAULT));
return true;
} else {
return false;
} }
if (evilTeamWaves.includes(waveIndex)) {
battleConfig
.setBattleType(BattleType.TRAINER)
.setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1)
.setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true));
return true;
}
if (waveIndex >= ClassicFixedBossWaves.ELITE_FOUR_1 && waveIndex <= ClassicFixedBossWaves.CHAMPION) {
const ttypes = trainerTypes as TrainerType[];
battleConfig
.setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(() => new Trainer(ttypes[this.value - 1], TrainerVariant.DEFAULT));
return true;
}
return false;
} }
/** /**
@ -556,10 +709,11 @@ export class SingleGenerationChallenge extends Challenge {
if (value === 0) { if (value === 0) {
return i18next.t("challenges:singleGeneration.desc_default"); return i18next.t("challenges:singleGeneration.desc_default");
} }
return i18next.t("challenges:singleGeneration.desc", { gen: i18next.t(`challenges:singleGeneration.gen_${value}`) }); return i18next.t("challenges:singleGeneration.desc", {
gen: i18next.t(`challenges:singleGeneration.gen_${value}`),
});
} }
static loadChallenge(source: SingleGenerationChallenge | any): SingleGenerationChallenge { static loadChallenge(source: SingleGenerationChallenge | any): SingleGenerationChallenge {
const newChallenge = new SingleGenerationChallenge(); const newChallenge = new SingleGenerationChallenge();
newChallenge.value = source.value; newChallenge.value = source.value;
@ -572,7 +726,7 @@ interface monotypeOverride {
/** The species to override */ /** The species to override */
species: Species; species: Species;
/** The type to count as */ /** The type to count as */
type: Type; type: PokemonType;
/** If part of a fusion, should we check the fused species instead of the base species? */ /** If part of a fusion, should we check the fused species instead of the base species? */
fusion: boolean; fusion: boolean;
} }
@ -582,20 +736,25 @@ interface monotypeOverride {
*/ */
export class SingleTypeChallenge extends Challenge { export class SingleTypeChallenge extends Challenge {
private static TYPE_OVERRIDES: monotypeOverride[] = [ private static TYPE_OVERRIDES: monotypeOverride[] = [
{ species: Species.CASTFORM, type: Type.NORMAL, fusion: false }, { species: Species.CASTFORM, type: PokemonType.NORMAL, fusion: false },
]; ];
// TODO: Find a solution for all Pokemon with this ssui issue, including Basculin and Burmy // 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() { constructor() {
super(Challenges.SINGLE_TYPE, 18); super(Challenges.SINGLE_TYPE, 18);
} }
override applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean { override applyStarterChoice(
pokemon: PokemonSpecies,
valid: Utils.BooleanHolder,
dexAttr: DexAttrProps,
soft = false,
): boolean {
const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex); const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex);
const types = [ speciesForm.type1, speciesForm.type2 ]; const types = [speciesForm.type1, speciesForm.type2];
if (soft && !SingleTypeChallenge.SPECIES_OVERRIDES.includes(pokemon.speciesId)) { if (soft && !SingleTypeChallenge.SPECIES_OVERRIDES.includes(pokemon.speciesId)) {
const speciesToCheck = [ pokemon.speciesId ]; const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) { while (speciesToCheck.length) {
const checking = speciesToCheck.pop(); const checking = speciesToCheck.pop();
if (checking && pokemonEvolutions.hasOwnProperty(checking)) { if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
@ -623,8 +782,16 @@ export class SingleTypeChallenge extends Challenge {
} }
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
if (pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true) if (
&& !SingleTypeChallenge.TYPE_OVERRIDES.some(o => o.type === (this.value - 1) && (pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies! : pokemon.species).speciesId === o.species)) { // TODO: is the bang on fusionSpecies correct? pokemon.isPlayer() &&
!pokemon.isOfType(this.value - 1, false, false, true) &&
!SingleTypeChallenge.TYPE_OVERRIDES.some(
o =>
o.type === this.value - 1 &&
(pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies! : pokemon.species).speciesId === o.species,
)
) {
// TODO: is the bang on fusionSpecies correct?
valid.value = false; valid.value = false;
return true; return true;
} }
@ -647,7 +814,7 @@ export class SingleTypeChallenge extends Challenge {
if (overrideValue === undefined) { if (overrideValue === undefined) {
overrideValue = this.value; overrideValue = this.value;
} }
return Type[this.value - 1].toLowerCase(); return PokemonType[this.value - 1].toLowerCase();
} }
/** /**
@ -659,10 +826,12 @@ export class SingleTypeChallenge extends Challenge {
if (overrideValue === undefined) { if (overrideValue === undefined) {
overrideValue = this.value; overrideValue = this.value;
} }
const type = i18next.t(`pokemonInfo:Type.${Type[this.value - 1]}`); const type = i18next.t(`pokemonInfo:Type.${PokemonType[this.value - 1]}`);
const typeColor = `[color=${TypeColor[Type[this.value - 1]]}][shadow=${TypeShadow[Type[this.value - 1]]}]${type}[/shadow][/color]`; const typeColor = `[color=${TypeColor[PokemonType[this.value - 1]]}][shadow=${TypeShadow[PokemonType[this.value - 1]]}]${type}[/shadow][/color]`;
const defaultDesc = i18next.t("challenges:singleType.desc_default"); const defaultDesc = i18next.t("challenges:singleType.desc_default");
const typeDesc = i18next.t("challenges:singleType.desc", { type: typeColor }); const typeDesc = i18next.t("challenges:singleType.desc", {
type: typeColor,
});
return this.value === 0 ? defaultDesc : typeDesc; return this.value === 0 ? defaultDesc : typeDesc;
} }
@ -702,12 +871,17 @@ export class FreshStartChallenge extends Challenge {
pokemon.abilityIndex = 0; // Always base ability, not hidden ability pokemon.abilityIndex = 0; // Always base ability, not hidden ability
pokemon.passive = false; // Passive isn't unlocked pokemon.passive = false; // Passive isn't unlocked
pokemon.nature = Nature.HARDY; // Neutral nature pokemon.nature = Nature.HARDY; // Neutral nature
pokemon.moveset = pokemon.species.getLevelMoves().filter(m => m[0] <= 5).map(lm => lm[1]).slice(0, 4).map(m => new PokemonMove(m)); // No egg moves pokemon.moveset = pokemon.species
.getLevelMoves()
.filter(m => m[0] <= 5)
.map(lm => lm[1])
.slice(0, 4)
.map(m => new PokemonMove(m)); // No egg moves
pokemon.luck = 0; // No luck pokemon.luck = 0; // No luck
pokemon.shiny = false; // Not shiny pokemon.shiny = false; // Not shiny
pokemon.variant = 0; // Not shiny pokemon.variant = 0; // Not shiny
pokemon.formIndex = 0; // Froakie should be base form 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 pokemon.teraType = pokemon.species.type1; // Always primary tera type
return true; return true;
} }
@ -747,7 +921,8 @@ export class InverseBattleChallenge extends Challenge {
if (effectiveness.value < 1) { if (effectiveness.value < 1) {
effectiveness.value = 2; effectiveness.value = 2;
return true; return true;
} else if (effectiveness.value > 1) { }
if (effectiveness.value > 1) {
effectiveness.value = 0.5; effectiveness.value = 0.5;
return true; return true;
} }
@ -764,7 +939,7 @@ export class FlipStatChallenge extends Challenge {
super(Challenges.FLIP_STAT, 1); super(Challenges.FLIP_STAT, 1);
} }
override applyFlipStat(pokemon: Pokemon, baseStats: number[]) { override applyFlipStat(_pokemon: Pokemon, baseStats: number[]) {
const origStats = Utils.deepCopy(baseStats); const origStats = Utils.deepCopy(baseStats);
baseStats[0] = origStats[5]; baseStats[0] = origStats[5];
baseStats[1] = origStats[4]; baseStats[1] = origStats[4];
@ -858,7 +1033,14 @@ export class LowerStarterPointsChallenge extends Challenge {
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon. * @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_CHOICE, pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.STARTER_CHOICE,
pokemon: PokemonSpecies,
valid: Utils.BooleanHolder,
dexAttr: DexAttrProps,
soft: boolean,
): boolean;
/** /**
* Apply all challenges that modify available total starter points. * Apply all challenges that modify available total starter points.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -866,7 +1048,11 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param points {@link Utils.NumberHolder} The amount of points you have available. * @param points {@link Utils.NumberHolder} The amount of points you have available.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_POINTS, points: Utils.NumberHolder): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.STARTER_POINTS,
points: Utils.NumberHolder,
): boolean;
/** /**
* Apply all challenges that modify the cost of a starter. * Apply all challenges that modify the cost of a starter.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -875,7 +1061,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param points {@link Utils.NumberHolder} The cost of the pokemon. * @param points {@link Utils.NumberHolder} The cost of the pokemon.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_COST, species: Species, cost: Utils.NumberHolder): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.STARTER_COST,
species: Species,
cost: Utils.NumberHolder,
): boolean;
/** /**
* Apply all challenges that modify a starter after selection. * Apply all challenges that modify a starter after selection.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -883,7 +1074,11 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param pokemon {@link Pokemon} The starter pokemon to modify. * @param pokemon {@link Pokemon} The starter pokemon to modify.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_MODIFY, pokemon: Pokemon): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.STARTER_MODIFY,
pokemon: Pokemon,
): boolean;
/** /**
* Apply all challenges that what pokemon you can have in battle. * Apply all challenges that what pokemon you can have in battle.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -892,7 +1087,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. * @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.POKEMON_IN_BATTLE, pokemon: Pokemon, valid: Utils.BooleanHolder): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.POKEMON_IN_BATTLE,
pokemon: Pokemon,
valid: Utils.BooleanHolder,
): boolean;
/** /**
* Apply all challenges that modify what fixed battles there are. * Apply all challenges that modify what fixed battles there are.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -901,7 +1101,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param battleConfig {@link FixedBattleConfig} The battle config to modify. * @param battleConfig {@link FixedBattleConfig} The battle config to modify.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FIXED_BATTLES, waveIndex: Number, battleConfig: FixedBattleConfig): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.FIXED_BATTLES,
waveIndex: number,
battleConfig: FixedBattleConfig,
): boolean;
/** /**
* Apply all challenges that modify type effectiveness. * Apply all challenges that modify type effectiveness.
* @param gameMode {@linkcode GameMode} The current gameMode * @param gameMode {@linkcode GameMode} The current gameMode
@ -909,7 +1114,11 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move. * @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.TYPE_EFFECTIVENESS, effectiveness: Utils.NumberHolder): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.TYPE_EFFECTIVENESS,
effectiveness: Utils.NumberHolder,
): boolean;
/** /**
* Apply all challenges that modify what level AI are. * Apply all challenges that modify what level AI are.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -920,7 +1129,14 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon. * @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.AI_LEVEL, level: Utils.NumberHolder, levelCap: number, isTrainer: boolean, isBoss: boolean): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.AI_LEVEL,
level: Utils.NumberHolder,
levelCap: number,
isTrainer: boolean,
isBoss: boolean,
): boolean;
/** /**
* Apply all challenges that modify how many move slots the AI has. * Apply all challenges that modify how many move slots the AI has.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -929,7 +1145,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param moveSlots {@link Utils.NumberHolder} The amount of move slots. * @param moveSlots {@link Utils.NumberHolder} The amount of move slots.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.AI_MOVE_SLOTS, pokemon: Pokemon, moveSlots: Utils.NumberHolder): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.AI_MOVE_SLOTS,
pokemon: Pokemon,
moveSlots: Utils.NumberHolder,
): boolean;
/** /**
* Apply all challenges that modify whether a pokemon has its passive. * Apply all challenges that modify whether a pokemon has its passive.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -938,7 +1159,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param hasPassive {@link Utils.BooleanHolder} Whether it has its passive. * @param hasPassive {@link Utils.BooleanHolder} Whether it has its passive.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.PASSIVE_ACCESS, pokemon: Pokemon, hasPassive: Utils.BooleanHolder): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.PASSIVE_ACCESS,
pokemon: Pokemon,
hasPassive: Utils.BooleanHolder,
): boolean;
/** /**
* Apply all challenges that modify the game modes settings. * Apply all challenges that modify the game modes settings.
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -956,7 +1182,14 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param level {@link Utils.NumberHolder} The level threshold for access. * @param level {@link Utils.NumberHolder} The level threshold for access.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_ACCESS, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.NumberHolder): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.MOVE_ACCESS,
pokemon: Pokemon,
moveSource: MoveSourceType,
move: Moves,
level: Utils.NumberHolder,
): boolean;
/** /**
* Apply all challenges that modify what weight a pokemon gives to move generation * Apply all challenges that modify what weight a pokemon gives to move generation
* @param gameMode {@link GameMode} The current gameMode * @param gameMode {@link GameMode} The current gameMode
@ -967,9 +1200,21 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
* @param weight {@link Utils.NumberHolder} The weight of the move. * @param weight {@link Utils.NumberHolder} The weight of the move.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_WEIGHT, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, weight: Utils.NumberHolder): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.MOVE_WEIGHT,
pokemon: Pokemon,
moveSource: MoveSourceType,
move: Moves,
weight: Utils.NumberHolder,
): boolean;
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FLIP_STAT, pokemon: Pokemon, baseStats: number[]): boolean; export function applyChallenges(
gameMode: GameMode,
challengeType: ChallengeType.FLIP_STAT,
pokemon: Pokemon,
baseStats: number[],
): boolean;
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean { export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean {
let ret = false; let ret = false;
@ -1057,6 +1302,6 @@ export function initChallenges() {
new SingleTypeChallenge(), new SingleTypeChallenge(),
new FreshStartChallenge(), new FreshStartChallenge(),
new InverseBattleChallenge(), new InverseBattleChallenge(),
new FlipStatChallenge() new FlipStatChallenge(),
); );
} }

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -24,17 +24,17 @@ export class EggHatchData {
} }
/** /**
* Sets the boolean for if the egg move for the hatch is a new unlock * Sets the boolean for if the egg move for the hatch is a new unlock
* @param unlocked True if the EM is new * @param unlocked True if the EM is new
*/ */
setEggMoveUnlocked(unlocked: boolean) { setEggMoveUnlocked(unlocked: boolean) {
this.eggMoveUnlocked = unlocked; this.eggMoveUnlocked = unlocked;
} }
/** /**
* Stores a copy of the current DexEntry of the pokemon and StarterDataEntry of its starter * 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 * Used before updating the dex, so comparing the pokemon to these entries will show the new attributes
*/ */
setDex() { setDex() {
const currDexEntry = globalScene.gameData.dexData[this.pokemon.species.speciesId]; const currDexEntry = globalScene.gameData.dexData[this.pokemon.species.speciesId];
const currStarterDataEntry = globalScene.gameData.starterData[this.pokemon.species.getRootSpeciesId()]; const currStarterDataEntry = globalScene.gameData.starterData[this.pokemon.species.getRootSpeciesId()];
@ -45,7 +45,7 @@ export class EggHatchData {
seenCount: currDexEntry.seenCount, seenCount: currDexEntry.seenCount,
caughtCount: currDexEntry.caughtCount, caughtCount: currDexEntry.caughtCount,
hatchedCount: currDexEntry.hatchedCount, hatchedCount: currDexEntry.hatchedCount,
ivs: [ ...currDexEntry.ivs ] ivs: [...currDexEntry.ivs],
}; };
this.starterDataEntryBeforeUpdate = { this.starterDataEntryBeforeUpdate = {
moveset: currStarterDataEntry.moveset, moveset: currStarterDataEntry.moveset,
@ -55,37 +55,37 @@ export class EggHatchData {
abilityAttr: currStarterDataEntry.abilityAttr, abilityAttr: currStarterDataEntry.abilityAttr,
passiveAttr: currStarterDataEntry.passiveAttr, passiveAttr: currStarterDataEntry.passiveAttr,
valueReduction: currStarterDataEntry.valueReduction, valueReduction: currStarterDataEntry.valueReduction,
classicWinCount: currStarterDataEntry.classicWinCount classicWinCount: currStarterDataEntry.classicWinCount,
}; };
} }
/** /**
* Gets the dex entry before update * Gets the dex entry before update
* @returns Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex * @returns Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex
*/ */
getDex(): DexEntry { getDex(): DexEntry {
return this.dexEntryBeforeUpdate; return this.dexEntryBeforeUpdate;
} }
/** /**
* Gets the starter dex entry before update * Gets the starter dex entry before update
* @returns Starter Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex * @returns Starter Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex
*/ */
getStarterEntry(): StarterDataEntry { getStarterEntry(): StarterDataEntry {
return this.starterDataEntryBeforeUpdate; return this.starterDataEntryBeforeUpdate;
} }
/** /**
* Update the pokedex data corresponding with the new hatch's pokemon data * Update the pokedex data corresponding with the new hatch's pokemon data
* Also sets whether the egg move is a new unlock or not * 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) * @param showMessage boolean to show messages for the new catches and egg moves (false by default)
* @returns * @returns
*/ */
updatePokemon(showMessage : boolean = false) { updatePokemon(showMessage = false) {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
globalScene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => { globalScene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => {
globalScene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs); globalScene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
globalScene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then((value) => { globalScene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then(value => {
this.setEggMoveUnlocked(value); this.setEggMoveUnlocked(value);
resolve(); resolve();
}); });

View File

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

View File

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

View File

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

View File

@ -0,0 +1,281 @@
import { Moves } from "#enums/moves";
/** Set of moves that cannot be called by {@linkcode Moves.METRONOME Metronome} */
export const invalidMetronomeMoves: ReadonlySet<Moves> = new Set([
Moves.AFTER_YOU,
Moves.APPLE_ACID,
Moves.ARMOR_CANNON,
Moves.ASSIST,
Moves.ASTRAL_BARRAGE,
Moves.AURA_WHEEL,
Moves.BANEFUL_BUNKER,
Moves.BEAK_BLAST,
Moves.BEHEMOTH_BASH,
Moves.BEHEMOTH_BLADE,
Moves.BELCH,
Moves.BESTOW,
Moves.BLAZING_TORQUE,
Moves.BODY_PRESS,
Moves.BRANCH_POKE,
Moves.BREAKING_SWIPE,
Moves.CELEBRATE,
Moves.CHATTER,
Moves.CHILLING_WATER,
Moves.CHILLY_RECEPTION,
Moves.CLANGOROUS_SOUL,
Moves.COLLISION_COURSE,
Moves.COMBAT_TORQUE,
Moves.COMEUPPANCE,
Moves.COPYCAT,
Moves.COUNTER,
Moves.COVET,
Moves.CRAFTY_SHIELD,
Moves.DECORATE,
Moves.DESTINY_BOND,
Moves.DETECT,
Moves.DIAMOND_STORM,
Moves.DOODLE,
Moves.DOUBLE_IRON_BASH,
Moves.DOUBLE_SHOCK,
Moves.DRAGON_ASCENT,
Moves.DRAGON_ENERGY,
Moves.DRUM_BEATING,
Moves.DYNAMAX_CANNON,
Moves.ELECTRO_DRIFT,
Moves.ENDURE,
Moves.ETERNABEAM,
Moves.FALSE_SURRENDER,
Moves.FEINT,
Moves.FIERY_WRATH,
Moves.FILLET_AWAY,
Moves.FLEUR_CANNON,
Moves.FOCUS_PUNCH,
Moves.FOLLOW_ME,
Moves.FREEZE_SHOCK,
Moves.FREEZING_GLARE,
Moves.GLACIAL_LANCE,
Moves.GRAV_APPLE,
Moves.HELPING_HAND,
Moves.HOLD_HANDS,
Moves.HYPER_DRILL,
Moves.HYPERSPACE_FURY,
Moves.HYPERSPACE_HOLE,
Moves.ICE_BURN,
Moves.INSTRUCT,
Moves.JET_PUNCH,
Moves.JUNGLE_HEALING,
Moves.KINGS_SHIELD,
Moves.LIFE_DEW,
Moves.LIGHT_OF_RUIN,
Moves.MAKE_IT_RAIN,
Moves.MAGICAL_TORQUE,
Moves.MAT_BLOCK,
Moves.ME_FIRST,
Moves.METEOR_ASSAULT,
Moves.METRONOME,
Moves.MIMIC,
Moves.MIND_BLOWN,
Moves.MIRROR_COAT,
Moves.MIRROR_MOVE,
Moves.MOONGEIST_BEAM,
Moves.NATURE_POWER,
Moves.NATURES_MADNESS,
Moves.NOXIOUS_TORQUE,
Moves.OBSTRUCT,
Moves.ORDER_UP,
Moves.ORIGIN_PULSE,
Moves.OVERDRIVE,
Moves.PHOTON_GEYSER,
Moves.PLASMA_FISTS,
Moves.POPULATION_BOMB,
Moves.POUNCE,
Moves.POWER_SHIFT,
Moves.PRECIPICE_BLADES,
Moves.PROTECT,
Moves.PYRO_BALL,
Moves.QUASH,
Moves.QUICK_GUARD,
Moves.RAGE_FIST,
Moves.RAGE_POWDER,
Moves.RAGING_BULL,
Moves.RAGING_FURY,
Moves.RELIC_SONG,
Moves.REVIVAL_BLESSING,
Moves.RUINATION,
Moves.SALT_CURE,
Moves.SECRET_SWORD,
Moves.SHED_TAIL,
Moves.SHELL_TRAP,
Moves.SILK_TRAP,
Moves.SKETCH,
Moves.SLEEP_TALK,
Moves.SNAP_TRAP,
Moves.SNARL,
Moves.SNATCH,
Moves.SNORE,
Moves.SNOWSCAPE,
Moves.SPECTRAL_THIEF,
Moves.SPICY_EXTRACT,
Moves.SPIKY_SHIELD,
Moves.SPIRIT_BREAK,
Moves.SPOTLIGHT,
Moves.STEAM_ERUPTION,
Moves.STEEL_BEAM,
Moves.STRANGE_STEAM,
Moves.STRUGGLE,
Moves.SUNSTEEL_STRIKE,
Moves.SURGING_STRIKES,
Moves.SWITCHEROO,
Moves.TECHNO_BLAST,
Moves.TERA_STARSTORM,
Moves.THIEF,
Moves.THOUSAND_ARROWS,
Moves.THOUSAND_WAVES,
Moves.THUNDER_CAGE,
Moves.THUNDEROUS_KICK,
Moves.TIDY_UP,
Moves.TRAILBLAZE,
Moves.TRANSFORM,
Moves.TRICK,
Moves.TWIN_BEAM,
Moves.V_CREATE,
Moves.WICKED_BLOW,
Moves.WICKED_TORQUE,
Moves.WIDE_GUARD,
]);
/** Set of moves that cannot be called by {@linkcode Moves.ASSIST Assist} */
export const invalidAssistMoves: ReadonlySet<Moves> = new Set([
Moves.ASSIST,
Moves.BANEFUL_BUNKER,
Moves.BEAK_BLAST,
Moves.BELCH,
Moves.BESTOW,
Moves.BOUNCE,
Moves.CELEBRATE,
Moves.CHATTER,
Moves.CIRCLE_THROW,
Moves.COPYCAT,
Moves.COUNTER,
Moves.COVET,
Moves.DESTINY_BOND,
Moves.DETECT,
Moves.DIG,
Moves.DIVE,
Moves.DRAGON_TAIL,
Moves.ENDURE,
Moves.FEINT,
Moves.FLY,
Moves.FOCUS_PUNCH,
Moves.FOLLOW_ME,
Moves.HELPING_HAND,
Moves.HOLD_HANDS,
Moves.KINGS_SHIELD,
Moves.MAT_BLOCK,
Moves.ME_FIRST,
Moves.METRONOME,
Moves.MIMIC,
Moves.MIRROR_COAT,
Moves.MIRROR_MOVE,
Moves.NATURE_POWER,
Moves.PHANTOM_FORCE,
Moves.PROTECT,
Moves.RAGE_POWDER,
Moves.ROAR,
Moves.SHADOW_FORCE,
Moves.SHELL_TRAP,
Moves.SKETCH,
Moves.SKY_DROP,
Moves.SLEEP_TALK,
Moves.SNATCH,
Moves.SPIKY_SHIELD,
Moves.SPOTLIGHT,
Moves.STRUGGLE,
Moves.SWITCHEROO,
Moves.THIEF,
Moves.TRANSFORM,
Moves.TRICK,
Moves.WHIRLWIND,
]);
/** Set of moves that cannot be called by {@linkcode Moves.SLEEP_TALK Sleep Talk} */
export const invalidSleepTalkMoves: ReadonlySet<Moves> = new Set([
Moves.ASSIST,
Moves.BELCH,
Moves.BEAK_BLAST,
Moves.BIDE,
Moves.BOUNCE,
Moves.COPYCAT,
Moves.DIG,
Moves.DIVE,
Moves.DYNAMAX_CANNON,
Moves.FREEZE_SHOCK,
Moves.FLY,
Moves.FOCUS_PUNCH,
Moves.GEOMANCY,
Moves.ICE_BURN,
Moves.ME_FIRST,
Moves.METRONOME,
Moves.MIRROR_MOVE,
Moves.MIMIC,
Moves.PHANTOM_FORCE,
Moves.RAZOR_WIND,
Moves.SHADOW_FORCE,
Moves.SHELL_TRAP,
Moves.SKETCH,
Moves.SKULL_BASH,
Moves.SKY_ATTACK,
Moves.SKY_DROP,
Moves.SLEEP_TALK,
Moves.SOLAR_BLADE,
Moves.SOLAR_BEAM,
Moves.STRUGGLE,
Moves.UPROAR,
]);
/** Set of moves that cannot be copied by {@linkcode Moves.COPYCAT Copycat} */
export const invalidCopycatMoves: ReadonlySet<Moves> = new Set([
Moves.ASSIST,
Moves.BANEFUL_BUNKER,
Moves.BEAK_BLAST,
Moves.BEHEMOTH_BASH,
Moves.BEHEMOTH_BLADE,
Moves.BESTOW,
Moves.CELEBRATE,
Moves.CHATTER,
Moves.CIRCLE_THROW,
Moves.COPYCAT,
Moves.COUNTER,
Moves.COVET,
Moves.DESTINY_BOND,
Moves.DETECT,
Moves.DRAGON_TAIL,
Moves.ENDURE,
Moves.FEINT,
Moves.FOCUS_PUNCH,
Moves.FOLLOW_ME,
Moves.HELPING_HAND,
Moves.HOLD_HANDS,
Moves.KINGS_SHIELD,
Moves.MAT_BLOCK,
Moves.ME_FIRST,
Moves.METRONOME,
Moves.MIMIC,
Moves.MIRROR_COAT,
Moves.MIRROR_MOVE,
Moves.PROTECT,
Moves.RAGE_POWDER,
Moves.ROAR,
Moves.SHELL_TRAP,
Moves.SKETCH,
Moves.SLEEP_TALK,
Moves.SNATCH,
Moves.SPIKY_SHIELD,
Moves.SPOTLIGHT,
Moves.STRUGGLE,
Moves.SWITCHEROO,
Moves.THIEF,
Moves.TRANSFORM,
Moves.TRICK,
Moves.WHIRLWIND,
]);

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,12 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
import { trainerConfigs, } from "#app/data/trainer-config"; initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
@ -27,161 +32,172 @@ const namespace = "mysteryEncounters/aTrainersTest";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3816 | GitHub Issue #3816} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3816 | GitHub Issue #3816}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ATrainersTestEncounter: MysteryEncounter = export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST) MysteryEncounterType.A_TRAINERS_TEST,
.withEncounterTier(MysteryEncounterTier.ROGUE) )
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withEncounterTier(MysteryEncounterTier.ROGUE)
.withIntroSpriteConfigs([]) // These are set in onInit() .withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withIntroDialogue([ .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) encounter.options[0].dialogue!.selected = [
.withOnInit(() => { {
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!; 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 await transitionMysteryEncounterIntroVisuals();
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;
}
// Dialogue and tokens for trainer const eggOptions: IEggOptions = {
encounter.dialogue.intro = [ pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.EPIC,
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
setEncounterRewards(
{ {
speaker: `trainerNames:${trainerNameKey}`, guaranteedModifierTypeFuncs: [modifierTypes.SACRED_ASH],
text: `${namespace}:${trainerNameKey}.intro_dialogue` guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA],
} fillRemaining: true,
];
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
}, },
{ [eggOptions],
spriteKey: trainerSpriteKey, );
fileRoot: "trainer", await initBattleWithEnemyConfig(config);
hasShadow: true, },
disableAnimation: true, )
x: -24, .withSimpleOption(
y: 4, {
yShadow: 4 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; const eggOptions: IEggOptions = {
}) pulled: false,
.setLocalizationKey(`${namespace}`) sourceType: EggSourceType.EVENT,
.withTitle(`${namespace}:title`) eggDescriptor: encounter.misc.trainerEggDescription,
.withDescription(`${namespace}:description`) tier: EggTier.RARE,
.withQuery(`${namespace}:query`) };
.withIntroDialogue() encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`));
.withSimpleOption( setEncounterRewards({ fillRemaining: false, rerollMultiplier: -1 }, [eggOptions]);
{ leaveEncounterWithoutBattle();
buttonLabel: `${namespace}:option.1.label`, },
buttonTooltip: `${namespace}:option.1.tooltip` )
}, .withOutroDialogue([
async () => { {
const encounter = globalScene.currentBattle.mysteryEncounter!; text: `${namespace}:outro`,
// Battle the stat trainer for an Egg and great rewards },
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; ])
.build();
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();

View File

@ -1,5 +1,11 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
@ -20,7 +26,11 @@ import { Moves } from "#enums/moves";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils"; import { randInt } from "#app/utils";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import {
applyModifierTypeToPlayerPokemon,
catchPokemon,
getHighestLevelPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#app/data/trainer-config";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
@ -38,339 +48,348 @@ const namespace = "mysteryEncounters/absoluteAvarice";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3805 | GitHub Issue #3805} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3805 | GitHub Issue #3805}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const AbsoluteAvariceEncounter: MysteryEncounter = export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.ABSOLUTE_AVARICE) MysteryEncounterType.ABSOLUTE_AVARICE,
.withEncounterTier(MysteryEncounterTier.GREAT) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Must have at least 4 berries to spawn .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withFleeAllowed(false) .withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Must have at least 4 berries to spawn
.withIntroSpriteConfigs([ .withFleeAllowed(false)
{ .withIntroSpriteConfigs([
// This sprite has the shadow {
spriteKey: "", // This sprite has the shadow
fileRoot: "", spriteKey: "",
species: Species.GREEDENT, fileRoot: "",
hasShadow: true, species: Species.GREEDENT,
alpha: 0.001, hasShadow: true,
repeat: true, alpha: 0.001,
x: -5 repeat: true,
}, x: -5,
{ },
spriteKey: "", {
fileRoot: "", spriteKey: "",
species: Species.GREEDENT, fileRoot: "",
hasShadow: false, species: Species.GREEDENT,
repeat: true, hasShadow: false,
x: -5 repeat: true,
}, x: -5,
{ },
spriteKey: "lum_berry", {
fileRoot: "items", spriteKey: "lum_berry",
isItem: true, fileRoot: "items",
x: 7, isItem: true,
y: -14, x: 7,
hidden: true, y: -14,
disableAnimation: true hidden: true,
}, disableAnimation: true,
{ },
spriteKey: "salac_berry", {
fileRoot: "items", spriteKey: "salac_berry",
isItem: true, fileRoot: "items",
x: 2, isItem: true,
y: 4, x: 2,
hidden: true, y: 4,
disableAnimation: true hidden: true,
}, disableAnimation: true,
{ },
spriteKey: "lansat_berry", {
fileRoot: "items", spriteKey: "lansat_berry",
isItem: true, fileRoot: "items",
x: 32, isItem: true,
y: 5, x: 32,
hidden: true, y: 5,
disableAnimation: true hidden: true,
}, disableAnimation: true,
{ },
spriteKey: "liechi_berry", {
fileRoot: "items", spriteKey: "liechi_berry",
isItem: true, fileRoot: "items",
x: 6, isItem: true,
y: -5, x: 6,
hidden: true, y: -5,
disableAnimation: true hidden: true,
}, disableAnimation: true,
{ },
spriteKey: "sitrus_berry", {
fileRoot: "items", spriteKey: "sitrus_berry",
isItem: true, fileRoot: "items",
x: 7, isItem: true,
y: 8, x: 7,
hidden: true, y: 8,
disableAnimation: true hidden: true,
}, disableAnimation: true,
{ },
spriteKey: "enigma_berry", {
fileRoot: "items", spriteKey: "enigma_berry",
isItem: true, fileRoot: "items",
x: 26, isItem: true,
y: -4, x: 26,
hidden: true, y: -4,
disableAnimation: true hidden: true,
}, disableAnimation: true,
{ },
spriteKey: "leppa_berry", {
fileRoot: "items", spriteKey: "leppa_berry",
isItem: true, fileRoot: "items",
x: 16, isItem: true,
y: -27, x: 16,
hidden: true, y: -27,
disableAnimation: true hidden: true,
}, disableAnimation: true,
{ },
spriteKey: "petaya_berry", {
fileRoot: "items", spriteKey: "petaya_berry",
isItem: true, fileRoot: "items",
x: 30, isItem: true,
y: -17, x: 30,
hidden: true, y: -17,
disableAnimation: true hidden: true,
}, disableAnimation: true,
{ },
spriteKey: "ganlon_berry", {
fileRoot: "items", spriteKey: "ganlon_berry",
isItem: true, fileRoot: "items",
x: 16, isItem: true,
y: -11, x: 16,
hidden: true, y: -11,
disableAnimation: true hidden: true,
}, disableAnimation: true,
{ },
spriteKey: "apicot_berry", {
fileRoot: "items", spriteKey: "apicot_berry",
isItem: true, fileRoot: "items",
x: 14, isItem: true,
y: -2, x: 14,
hidden: true, y: -2,
disableAnimation: true hidden: true,
}, disableAnimation: true,
{ },
spriteKey: "starf_berry", {
fileRoot: "items", spriteKey: "starf_berry",
isItem: true, fileRoot: "items",
x: 18, isItem: true,
y: 9, x: 18,
hidden: true, y: 9,
disableAnimation: true hidden: true,
}, disableAnimation: true,
]) },
.withHideWildIntroMessage(true) ])
.withAutoHideIntroVisuals(false) .withHideWildIntroMessage(true)
.withIntroDialogue([ .withAutoHideIntroVisuals(false)
{ .withIntroDialogue([
text: `${namespace}:intro`, {
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"); encounter.misc = { berryItemsMap };
globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
// Get all player berry items, remove from party, and store reference // Generates copies of the stolen berries to put on the Greedent
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; 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 // Do NOT remove the real berries yet or else it will be persisted in the session data
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);
}
});
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 // Calculate boss mon
const bossModifierConfigs: HeldModifierConfig[] = []; const config: EnemyPartyConfig = {
berryItems.forEach(berryMod => { levelAdditiveModifier: 1,
// Can't define stack count on a ModifierType, have to just create separate instances for each stack pokemonConfigs: [
// Overflow berries will be "lost" on the boss, but it's un-catchable anyway {
for (let i = 0; i < berryMod.stackCount; i++) { species: getPokemonSpecies(Species.GREEDENT),
const modifierType = generateModifierType(modifierTypes.BERRY, [ berryMod.berryType ]) as PokemonHeldItemModifierType; isBoss: true,
bossModifierConfigs.push({ modifier: modifierType }); 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 return true;
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ? })
[ Stat.SPDEF ] : .withOnVisualsStart(() => {
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; doGreedentSpriteSteal();
doBerrySpritePile();
// Calculate boss mon // Remove the berries from the party
const config: EnemyPartyConfig = { // Session has been safely saved at this point, so data won't be lost
levelAdditiveModifier: 1, const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
pokemonConfigs: [ 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), text: `${namespace}:option.1.selected`,
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 ], .withOptionPhase(async () => {
modifierConfigs: bossModifierConfigs, // Pick battle
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], const encounter = globalScene.currentBattle.mysteryEncounter!;
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.boss_enraged`); // Provides 1x Reviver Seed to each party member at end of battle
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); 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 ]; await transitionMysteryEncounterIntroVisuals(true, true, 500);
encounter.setDialogueToken("greedentName", getPokemonSpecies(Species.GREEDENT).getName()); await catchPokemon(greedent, null, PokeballType.POKEBALL, false);
leaveEncounterWithoutBattle(true);
return true; })
}) .build(),
.withOnVisualsStart(() => { )
doGreedentSpriteSteal(); .build();
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();
function doGreedentSpriteSteal() { function doGreedentSpriteSteal() {
const shakeDelay = 50; const shakeDelay = 50;
@ -382,70 +401,79 @@ function doGreedentSpriteSteal() {
globalScene.tweens.chain({ globalScene.tweens.chain({
targets: greedentSprites, targets: greedentSprites,
tweens: [ tweens: [
{ // Slide Greedent diagonally {
// Slide Greedent diagonally
duration: slideDelay, duration: slideDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
y: "+=75", y: "+=75",
x: "-=65", x: "-=65",
scale: 1.1 scale: 1.1,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Shake {
// Shake
duration: shakeDelay, duration: shakeDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
x: (randInt(2) > 0 ? "-=" : "+=") + 5, x: (randInt(2) > 0 ? "-=" : "+=") + 5,
y: (randInt(2) > 0 ? "-=" : "+=") + 5, y: (randInt(2) > 0 ? "-=" : "+=") + 5,
}, },
{ // Slide Greedent diagonally {
// Slide Greedent diagonally
duration: slideDelay, duration: slideDelay,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
y: "-=75", y: "-=75",
x: "+=65", x: "+=65",
scale: 1 scale: 1,
}, },
{ // Bounce at the end {
// Bounce at the end
duration: 300, duration: 300,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
yoyo: true, yoyo: true,
y: "-=20", y: "-=20",
loop: 1, loop: 1,
} },
] ],
}); });
} }
@ -467,16 +495,28 @@ function doGreedentEatBerries() {
globalScene.playSound("battle_anims/PRSFX- Bug Bite"); globalScene.playSound("battle_anims/PRSFX- Bug Bite");
} }
index++; index++;
} },
}); });
} }
/** /**
* @param isEat Default false. Will "create" pile when false, and remove pile when true. * @param isEat Default false. Will "create" pile when false, and remove pile when true.
*/ */
function doBerrySpritePile(isEat: boolean = false) { function doBerrySpritePile(isEat = false) {
const berryAddDelay = 150; const berryAddDelay = 150;
let animationOrder = [ "starf", "sitrus", "lansat", "salac", "apicot", "enigma", "liechi", "ganlon", "lum", "petaya", "leppa" ]; let animationOrder = [
"starf",
"sitrus",
"lansat",
"salac",
"apicot",
"enigma",
"liechi",
"ganlon",
"lum",
"petaya",
"leppa",
];
if (isEat) { if (isEat) {
animationOrder = animationOrder.reverse(); animationOrder = animationOrder.reverse();
} }
@ -500,7 +540,7 @@ function doBerrySpritePile(isEat: boolean = false) {
// Animate Petaya berry falling off the pile // Animate Petaya berry falling off the pile
if (berry === "petaya" && sprite && tintSprite && !isEat) { if (berry === "petaya" && sprite && tintSprite && !isEat) {
globalScene.time.delayedCall(200, () => { 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({ globalScene.tweens.add({
targets: berrySprites, targets: berrySprites,
y: "+=" + bounceYOffset, y: "+=" + bounceYOffset,
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" }, x: { value: "+=" + bouncePower * bouncePower * 10, ease: "Linear" },
duration: bouncePower * baseBounceDuration, duration: bouncePower * baseBounceDuration,
ease: "Cubic.easeIn", ease: "Cubic.easeIn",
onComplete: () => { onComplete: () => {
@ -527,13 +567,13 @@ function doBerryBounce(berrySprites: Phaser.GameObjects.Sprite[], yd: number, ba
globalScene.tweens.add({ globalScene.tweens.add({
targets: berrySprites, targets: berrySprites,
y: "-=" + bounceYOffset, y: "-=" + bounceYOffset,
x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" }, x: { value: "+=" + bouncePower * bouncePower * 10, ease: "Linear" },
duration: bouncePower * baseBounceDuration, duration: bouncePower * baseBounceDuration,
ease: "Cubic.easeOut", ease: "Cubic.easeOut",
onComplete: () => doBounce() onComplete: () => doBounce(),
}); });
} }
} },
}); });
}; };

View File

@ -1,4 +1,9 @@
import { generateModifierType, leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
generateModifierType,
leaveEncounterWithoutBattle,
setEncounterExp,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
@ -6,7 +11,11 @@ import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import {
AbilityRequirement,
CombinationPokemonRequirement,
MoveRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
@ -33,153 +42,152 @@ const MONEY_MAXIMUM_MULTIPLIER = 30;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const AnOfferYouCantRefuseEncounter: MysteryEncounter = export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE) MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE,
.withEncounterTier(MysteryEncounterTier.GREAT) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.GREAT)
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([ .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
{ .withIntroSpriteConfigs([
spriteKey: Species.LIEPARD.toString(), {
fileRoot: "pokemon", spriteKey: Species.LIEPARD.toString(),
hasShadow: true, fileRoot: "pokemon",
repeat: true, hasShadow: true,
x: 0, repeat: true,
y: -4, x: 0,
yShadow: -4 y: -4,
}, yShadow: -4,
{ },
spriteKey: "rich_kid_m", {
fileRoot: "trainer", spriteKey: "rich_kid_m",
hasShadow: true, fileRoot: "trainer",
x: 2, hasShadow: true,
y: 5, x: 2,
yShadow: 5 y: 5,
}, yShadow: 5,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
{ },
text: `${namespace}:intro_dialogue`, {
speaker: `${namespace}:speaker`, text: `${namespace}:intro_dialogue`,
}, speaker: `${namespace}:speaker`,
]) },
.setLocalizationKey(`${namespace}`) ])
.withTitle(`${namespace}:title`) .setLocalizationKey(`${namespace}`)
.withDescription(`${namespace}:description`) .withTitle(`${namespace}:title`)
.withQuery(`${namespace}:query`) .withDescription(`${namespace}:description`)
.withOnInit(() => { .withQuery(`${namespace}:query`)
const encounter = globalScene.currentBattle.mysteryEncounter!; .withOnInit(() => {
const pokemon = getHighestStatTotalPlayerPokemon(true, true); const encounter = globalScene.currentBattle.mysteryEncounter!;
const pokemon = getHighestStatTotalPlayerPokemon(true, true);
const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId(); const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId();
const starterValue: number = speciesStarterCosts[baseSpecies] ?? 1; const starterValue: number = speciesStarterCosts[baseSpecies] ?? 1;
const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER); const multiplier = Math.max((MONEY_MAXIMUM_MULTIPLIER / 10) * starterValue, MONEY_MINIMUM_MULTIPLIER);
const price = globalScene.getWaveMoneyAmount(multiplier); const price = globalScene.getWaveMoneyAmount(multiplier);
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender()); encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
encounter.setDialogueToken("price", price.toString()); encounter.setDialogueToken("price", price.toString());
// Store pokemon and price // Store pokemon and price
encounter.misc = { encounter.misc = {
pokemon: pokemon, pokemon: pokemon,
price: price price: price,
}; };
// If player meets the combo OR requirements for option 2, populate the token // If player meets the combo OR requirements for option 2, populate the token
const opt2Req = encounter.options[1].primaryPokemonRequirements[0]; const opt2Req = encounter.options[1].primaryPokemonRequirements[0];
if (opt2Req.meetsRequirement()) { if (opt2Req.meetsRequirement()) {
const abilityToken = encounter.dialogueTokens["option2PrimaryAbility"]; const abilityToken = encounter.dialogueTokens["option2PrimaryAbility"];
const moveToken = encounter.dialogueTokens["option2PrimaryMove"]; const moveToken = encounter.dialogueTokens["option2PrimaryMove"];
if (abilityToken) { if (abilityToken) {
encounter.setDialogueToken("moveOrAbility", abilityToken); encounter.setDialogueToken("moveOrAbility", abilityToken);
} else if (moveToken) { } else if (moveToken) {
encounter.setDialogueToken("moveOrAbility", moveToken); encounter.setDialogueToken("moveOrAbility", moveToken);
}
} }
}
const shinyCharm = generateModifierType(modifierTypes.SHINY_CHARM); const shinyCharm = generateModifierType(modifierTypes.SHINY_CHARM);
encounter.setDialogueToken("itemName", shinyCharm?.name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name")); encounter.setDialogueToken("itemName", shinyCharm?.name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name"));
encounter.setDialogueToken("liepardName", getPokemonSpecies(Species.LIEPARD).getName()); encounter.setDialogueToken("liepardName", getPokemonSpecies(Species.LIEPARD).getName());
return true; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .withDialogue({
.withDialogue({ buttonLabel: `${namespace}:option.1.label`,
buttonLabel: `${namespace}:option.1.label`, buttonTooltip: `${namespace}:option.1.tooltip`,
buttonTooltip: `${namespace}:option.1.tooltip`, selected: [
selected: [ {
{ text: `${namespace}:option.1.selected`,
text: `${namespace}:option.1.selected`, speaker: `${namespace}:speaker`,
speaker: `${namespace}:speaker`, },
}, ],
], })
}) .withPreOptionPhase(async (): Promise<boolean> => {
.withPreOptionPhase(async (): Promise<boolean> => { const encounter = globalScene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!; // Update money and remove pokemon from party
// Update money and remove pokemon from party updatePlayerMoney(encounter.misc.price);
updatePlayerMoney(encounter.misc.price); globalScene.removePokemonFromPlayerParty(encounter.misc.pokemon);
globalScene.removePokemonFromPlayerParty(encounter.misc.pokemon); return true;
return true; })
}) .withOptionPhase(async () => {
.withOptionPhase(async () => { // Give the player a Shiny Charm
// Give the player a Shiny Charm globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM));
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM)); leaveEncounterWithoutBattle(true);
leaveEncounterWithoutBattle(true); })
}) .build(),
.build() )
) .withOption(
.withOption( MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
MysteryEncounterOptionBuilder .withPrimaryPokemonRequirement(
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) CombinationPokemonRequirement.Some(
.withPrimaryPokemonRequirement( new MoveRequirement(EXTORTION_MOVES, true),
CombinationPokemonRequirement.Some( new AbilityRequirement(EXTORTION_ABILITIES, true),
new MoveRequirement(EXTORTION_MOVES, true), ),
new AbilityRequirement(EXTORTION_ABILITIES, true) )
) .withDialogue({
) buttonLabel: `${namespace}:option.2.label`,
.withDialogue({ buttonTooltip: `${namespace}:option.2.tooltip`,
buttonLabel: `${namespace}:option.2.label`, disabledButtonTooltip: `${namespace}:option.2.tooltip_disabled`,
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`,
selected: [ selected: [
{ {
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}:option.3.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, })
async () => { .withOptionPhase(async () => {
// Leave encounter with no rewards or exp // 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); 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();

View File

@ -1,6 +1,5 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { import {
generateModifierType, generateModifierType,
generateModifierTypeOption, generateModifierTypeOption,
@ -8,18 +7,12 @@ import {
initBattleWithEnemyConfig, initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, leaveEncounterWithoutBattle,
setEncounterExp, setEncounterExp,
setEncounterRewards setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
BerryModifierType, import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
ModifierTypeOption } from "#app/modifier/modifier-type";
import {
ModifierPoolType,
modifierTypes,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -30,7 +23,13 @@ import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-enco
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { applyModifierTypeToPlayerPokemon, getEncounterPokemonLevelForWave, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import {
applyModifierTypeToPlayerPokemon,
getEncounterPokemonLevelForWave,
getHighestStatPlayerPokemon,
getSpriteKeysFromPokemon,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier } from "#app/modifier/modifier";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
@ -47,107 +46,150 @@ const namespace = "mysteryEncounters/berriesAbound";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3810 | GitHub Issue #3810} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3810 | GitHub Issue #3810}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const BerriesAboundEncounter: MysteryEncounter = export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BERRIES_ABOUND) MysteryEncounterType.BERRIES_ABOUND,
.withEncounterTier(MysteryEncounterTier.COMMON) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.COMMON)
.withCatchAllowed(true) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withHideWildIntroMessage(true) .withCatchAllowed(true)
.withFleeAllowed(false) .withHideWildIntroMessage(true)
.withIntroSpriteConfigs([]) // Set in onInit() .withFleeAllowed(false)
.withIntroDialogue([ .withIntroSpriteConfigs([]) // Set in onInit()
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
]) },
.withOnInit(() => { ])
const encounter = globalScene.currentBattle.mysteryEncounter!; .withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mon // Calculate boss mon
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossPokemon = getRandomEncounterSpecies(level, true); const bossPokemon = getRandomEncounterSpecies(level, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
pokemonConfigs: [{ pokemonConfigs: [
{
level: level, level: level,
species: bossPokemon.species, species: bossPokemon.species,
dataSource: new PokemonData(bossPokemon), dataSource: new PokemonData(bossPokemon),
isBoss: true isBoss: true,
}],
};
encounter.enemyPartyConfigs = [ config ];
// 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
}, },
{ ],
spriteKey: spriteKey, };
fileRoot: fileRoot, encounter.enemyPartyConfigs = [config];
hasShadow: true,
tint: 0.25,
x: -5,
repeat: true,
isPokemon: true,
isShiny: bossPokemon.shiny,
variant: bossPokemon.variant
}
];
// Get fastest party pokemon for option 2 // Calculate the number of extra berries that player receives
const fastestPokemon = getHighestStatPlayerPokemon(PERMANENT_STATS[Stat.SPD], true, false); // 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7
encounter.misc.fastestPokemon = fastestPokemon; const numBerries =
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD); globalScene.currentBattle.waveIndex > 160
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender()); ? 7
: globalScene.currentBattle.waveIndex > 120
? 5
: globalScene.currentBattle.waveIndex > 40
? 4
: 2;
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
encounter.misc = { numBerries };
return true; const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
}) encounter.spriteConfigs = [
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withSimpleOption(
{ {
buttonLabel: `${namespace}:option.1.label`, spriteKey: "berries_abound_bush",
buttonTooltip: `${namespace}:option.1.tooltip`, fileRoot: "mystery-encounters",
selected: [ x: 25,
{ y: -6,
text: `${namespace}:option.1.selected`, 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 encounter = globalScene.currentBattle.mysteryEncounter!;
const numBerries = encounter.misc.numBerries; const fastestPokemon: PlayerPokemon = encounter.misc.fastestPokemon;
const enemySpeed: number = encounter.misc.enemySpeed;
const doBerryRewards = () => { const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1);
const berryText = i18next.t(`${namespace}:berries`); const numBerries: number = encounter.misc.numBerries;
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[] = []; const shopOptions: ModifierTypeOption[] = [];
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
@ -158,115 +200,126 @@ export const BerriesAboundEncounter: MysteryEncounter =
} }
} }
setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards); if (speedDiff < 1) {
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); // Caught and attacked by boss, gets +1 to all stats at start of fight
} const doBerryRewards = () => {
) const berryText = i18next.t(`${namespace}:berries`);
.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;
const shopOptions: ModifierTypeOption[] = []; globalScene.playSound("item_fanfare");
for (let i = 0; i < 5; i++) { queueEncounterMessage(
// Generate shop berries i18next.t("battle:rewardGainCount", {
const mod = generateModifierTypeOption(modifierTypes.BERRY); modifierName: berryText,
if (mod) { count: numBerries,
shopOptions.push(mod); }),
);
// 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) { setEncounterExp(fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
// Caught and attacked by boss, gets +1 to all stats at start of fight setEncounterRewards(
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: [
{ {
text: `${namespace}:option.3.selected`, guaranteedModifierTypeOptions: shopOptions,
fillRemaining: false,
}, },
], undefined,
}, doFasterBerryRewards,
async () => { );
// Leave encounter with no rewards or exp await showEncounterText(`${namespace}:option.2.selected`);
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle();
return true; })
} .build(),
) )
.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) { function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType; const berryType = randSeedInt(Object.keys(BerryType).filter(s => !Number.isNaN(Number(s))).length) as BerryType;
const berry = generateModifierType(modifierTypes.BERRY, [ berryType ]) as BerryModifierType; const berry = generateModifierType(modifierTypes.BERRY, [berryType]) as BerryModifierType;
const party = globalScene.getPlayerParty(); const party = globalScene.getPlayerParty();
// Will try to apply to prioritized pokemon first, then do normal application method if it fails // Will try to apply to prioritized pokemon first, then do normal application method if it fails
if (prioritizedPokemon) { if (prioritizedPokemon) {
const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier const heldBerriesOfType = globalScene.findModifier(
&& m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier; m =>
m instanceof BerryModifier &&
m.pokemonId === prioritizedPokemon.id &&
(m as BerryModifier).berryType === berryType,
true,
) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry); applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry);
@ -276,8 +329,10 @@ function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
// Iterate over the party until berry was successfully given // Iterate over the party until berry was successfully given
for (const pokemon of party) { for (const pokemon of party) {
const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier const heldBerriesOfType = globalScene.findModifier(
&& m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier; m => m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType,
true,
) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
applyModifierTypeToPlayerPokemon(pokemon, berry); applyModifierTypeToPlayerPokemon(pokemon, berry);

View File

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

View File

@ -1,6 +1,14 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config"; generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
loadCustomMovesForEncounter,
selectPokemonForOption,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type"; import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type";
@ -14,8 +22,11 @@ import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { applyAbilityOverrideToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import {
import { Type } from "#enums/type"; 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 { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { randSeedInt, randSeedShuffle } from "#app/utils"; import { randSeedInt, randSeedShuffle } from "#app/utils";
@ -31,7 +42,7 @@ import { BerryType } from "#enums/berry-type";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { EncounterBattleAnim } from "#app/data/battle-anims"; import { EncounterBattleAnim } from "#app/data/battle-anims";
import { MoveCategory } from "#app/data/move"; import { MoveCategory } from "#enums/MoveCategory";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";
@ -55,7 +66,7 @@ const RANDOM_ABILITY_POOL = [
Abilities.MISTY_SURGE, Abilities.MISTY_SURGE,
Abilities.MAGICIAN, Abilities.MAGICIAN,
Abilities.SHEER_FORCE, Abilities.SHEER_FORCE,
Abilities.PRANKSTER Abilities.PRANKSTER,
]; ];
/** /**
@ -63,340 +74,363 @@ const RANDOM_ABILITY_POOL = [
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3807 | GitHub Issue #3807} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3807 | GitHub Issue #3807}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ClowningAroundEncounter: MysteryEncounter = export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND) MysteryEncounterType.CLOWNING_AROUND,
.withEncounterTier(MysteryEncounterTier.ULTRA) )
.withDisallowedChallenges(Challenges.SINGLE_TYPE) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withDisallowedChallenges(Challenges.SINGLE_TYPE)
.withAnimations(EncounterAnim.SMOKESCREEN) .withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withAutoHideIntroVisuals(false) .withAnimations(EncounterAnim.SMOKESCREEN)
.withIntroSpriteConfigs([ .withAutoHideIntroVisuals(false)
{ .withIntroSpriteConfigs([
spriteKey: Species.MR_MIME.toString(), {
fileRoot: "pokemon", spriteKey: Species.MR_MIME.toString(),
hasShadow: true, fileRoot: "pokemon",
repeat: true, hasShadow: true,
x: -25, repeat: true,
tint: 0.3, x: -25,
y: -3, tint: 0.3,
yShadow: -3 y: -3,
}, yShadow: -3,
{ },
spriteKey: Species.BLACEPHALON.toString(), {
fileRoot: "pokemon/exp", spriteKey: Species.BLACEPHALON.toString(),
hasShadow: true, fileRoot: "pokemon/exp",
repeat: true, hasShadow: true,
x: 25, repeat: true,
tint: 0.3, x: 25,
y: -3, tint: 0.3,
yShadow: -3 y: -3,
}, yShadow: -3,
{ },
spriteKey: "harlequin", {
fileRoot: "trainer", spriteKey: "harlequin",
hasShadow: true, fileRoot: "trainer",
x: 0, hasShadow: true,
y: 2, x: 0,
yShadow: 2 y: 2,
}, yShadow: 2,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
{ },
text: `${namespace}:intro_dialogue`, {
speaker: `${namespace}:speaker` text: `${namespace}:intro_dialogue`,
}, speaker: `${namespace}:speaker`,
]) },
.withOnInit(() => { ])
const encounter = globalScene.currentBattle.mysteryEncounter!; .withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const clownTrainerType = TrainerType.HARLEQUIN; const clownTrainerType = TrainerType.HARLEQUIN;
const clownConfig = trainerConfigs[clownTrainerType].clone(); const clownConfig = trainerConfigs[clownTrainerType].clone();
const clownPartyTemplate = new TrainerPartyCompoundTemplate( const clownPartyTemplate = new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG), new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER)); new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
clownConfig.setPartyTemplates(clownPartyTemplate); );
clownConfig.setDoubleOnly(); clownConfig.setPartyTemplates(clownPartyTemplate);
// @ts-ignore clownConfig.setDoubleOnly();
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists // @ts-ignore
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
// Generate random ability for Blacephalon from pool // Generate random ability for Blacephalon from pool
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)]; const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
encounter.setDialogueToken("ability", new Ability(ability, 3).name); encounter.setDialogueToken("ability", new Ability(ability, 3).name);
encounter.misc = { ability }; encounter.misc = { ability };
// Decide the random types for Blacephalon. They should not be the same. // Decide the random types for Blacephalon. They should not be the same.
const firstType: number = randSeedInt(18); const firstType: number = randSeedInt(18);
let secondType: number = randSeedInt(17); let secondType: number = randSeedInt(17);
if ( secondType >= firstType ) { if (secondType >= firstType) {
secondType++; secondType++;
} }
encounter.enemyPartyConfigs.push({ encounter.enemyPartyConfigs.push({
trainerConfig: clownConfig, trainerConfig: clownConfig,
pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon pokemonConfigs: [
// Overrides first 2 pokemon to be Mr. Mime and Blacephalon
{
species: getPokemonSpecies(Species.MR_MIME),
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), text: `${namespace}:option.1.selected`,
isBoss: true, speaker: `${namespace}:speaker`,
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 })
}); .withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Spawn battle
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
// Load animations/sfx for start of fight moves setEncounterRewards({ fillRemaining: true });
loadCustomMovesForEncounter([ Moves.ROLE_PLAY, Moves.TAUNT ]);
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; await transitionMysteryEncounterIntroVisuals();
}) await initBattleWithEnemyConfig(config);
.setLocalizationKey(`${namespace}`) })
.withTitle(`${namespace}:title`) .withPostOptionPhase(async (): Promise<boolean> => {
.withDescription(`${namespace}:description`) // After the battle, offer the player the opportunity to permanently swap ability
.withQuery(`${namespace}:query`) const abilityWasSwapped = await handleSwapAbility();
.withOption( if (abilityWasSwapped) {
MysteryEncounterOptionBuilder await showEncounterText(`${namespace}:option.1.ability_gained`);
.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];
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 const party = globalScene.getPlayerParty();
encounter.startOfBattleEffects.push( let mostHeldItemsPokemon = party[0];
{ // Mr. Mime copies the Blacephalon's random ability let count = mostHeldItemsPokemon
sourceBattlerIndex: BattlerIndex.ENEMY, .getHeldItems()
targets: [ BattlerIndex.ENEMY_2 ], .filter(m => m.isTransferable && !(m instanceof BerryModifier))
move: new PokemonMove(Moves.ROLE_PLAY), .reduce((v, m) => v + m.stackCount, 0);
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
});
await transitionMysteryEncounterIntroVisuals(); for (const pokemon of party) {
await initBattleWithEnemyConfig(config); const nextCount = pokemon
}) .getHeldItems()
.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()
.filter(m => m.isTransferable && !(m instanceof BerryModifier)) .filter(m => m.isTransferable && !(m instanceof BerryModifier))
.reduce((v, m) => v + m.stackCount, 0); .reduce((v, m) => v + m.stackCount, 0);
if (nextCount > count) {
mostHeldItemsPokemon = pokemon;
count = nextCount;
}
}
party.forEach(pokemon => { encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender());
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()); 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) generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries");
let numBerries = 0;
items.filter(m => m instanceof BerryModifier)
.forEach(m => {
numBerries += m.stackCount;
globalScene.removeModifier(m);
});
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 (const m of items.filter(m => m.isTransferable && !(m instanceof BerryModifier))) {
// For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier const type = m.type.withTierFromPool(ModifierPoolType.PLAYER, party);
// And Golden Eggs as Rogue tier const tier = type.tier ?? ModifierTier.ULTRA;
let numUltra = 0; if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
let numRogue = 0; numRogue += m.stackCount;
items.filter(m => m.isTransferable && !(m instanceof BerryModifier)) globalScene.removeModifier(m);
.forEach(m => { } else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) {
const type = m.type.withTierFromPool(ModifierPoolType.PLAYER, party); numUltra += m.stackCount;
const tier = type.tier ?? ModifierTier.ULTRA; globalScene.removeModifier(m);
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, numUltra, ModifierTier.ULTRA);
generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE); generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.withPostOptionPhase(async () => { .withPostOptionPhase(async () => {
// Play animations // Play animations
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon()); const background = new EncounterBattleAnim(
background.playWithoutTargets(230, 40, 2); EncounterAnim.SMOKESCREEN,
await transitionMysteryEncounterIntroVisuals(true, true, 200); globalScene.getPlayerPokemon()!,
}) globalScene.getPlayerPokemon(),
.build() );
) background.playWithoutTargets(230, 40, 2);
.withOption( await transitionMysteryEncounterIntroVisuals(true, true, 200);
MysteryEncounterOptionBuilder })
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .build(),
.withDialogue({ )
buttonLabel: `${namespace}:option.3.label`, .withOption(
buttonTooltip: `${namespace}:option.3.tooltip`, MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
selected: [ .withDialogue({
{ buttonLabel: `${namespace}:option.3.label`,
text: `${namespace}:option.3.selected`, buttonTooltip: `${namespace}:option.3.tooltip`,
speaker: `${namespace}:speaker` selected: [
}, {
{ text: `${namespace}:option.3.selected`,
text: `${namespace}:option.3.selected_2`, speaker: `${namespace}:speaker`,
}, },
{ {
text: `${namespace}:option.3.selected_3`, text: `${namespace}:option.3.selected_2`,
speaker: `${namespace}:speaker` },
}, {
], 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()) { .withPreOptionPhase(async () => {
const originalTypes = pokemon.getTypes(false, false, true); // 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 // If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type
// Makes the "randomness" of the shuffle slightly less punishing // Makes the "randomness" of the shuffle slightly less punishing
let priorityTypes = pokemon.moveset let priorityTypes = pokemon.moveset
.filter(move => move && !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS) .filter(
.map(move => move!.getMove().type); move =>
if (priorityTypes?.length > 0) { move && !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS,
priorityTypes = [ ...new Set(priorityTypes) ].sort(); )
priorityTypes = randSeedShuffle(priorityTypes); .map(move => move!.getMove().type);
} if (priorityTypes?.length > 0) {
priorityTypes = [...new Set(priorityTypes)].sort();
priorityTypes = randSeedShuffle(priorityTypes);
}
const newTypes = [ Type.UNKNOWN ]; const newTypes = [PokemonType.UNKNOWN];
let secondType: Type | null = null; let secondType: PokemonType | null = null;
while (secondType === null || secondType === newTypes[0] || originalTypes.includes(secondType)) { while (secondType === null || secondType === newTypes[0] || originalTypes.includes(secondType)) {
if (priorityTypes.length > 0) { if (priorityTypes.length > 0) {
secondType = priorityTypes.pop() ?? null; secondType = priorityTypes.pop() ?? null;
} else { } else {
secondType = randSeedInt(18) as Type; 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;
} }
} }
}) newTypes.push(secondType);
.withOptionPhase(async () => {
leaveEncounterWithoutBattle(true); // Apply the type changes (to both base and fusion, if pokemon is fused)
}) if (!pokemon.customPokemonData) {
.withPostOptionPhase(async () => { pokemon.customPokemonData = new CustomPokemonData();
// Play animations }
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon()); pokemon.customPokemonData.types = newTypes;
background.playWithoutTargets(230, 40, 2); if (pokemon.isFusion()) {
await transitionMysteryEncounterIntroVisuals(true, true, 200); if (!pokemon.fusionCustomPokemonData) {
}) pokemon.fusionCustomPokemonData = new CustomPokemonData();
.build() }
) pokemon.fusionCustomPokemonData.types = newTypes;
.withOutroDialogue([ }
{ }
text: `${namespace}:outro`, })
}, .withOptionPhase(async () => {
]) leaveEncounterWithoutBattle(true);
.build(); })
.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() { async function handleSwapAbility() {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
return new Promise<boolean>(async resolve => { return new Promise<boolean>(async resolve => {
await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`); await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`);
await showEncounterText(`${namespace}:option.1.apply_ability_message`); await showEncounterText(`${namespace}:option.1.apply_ability_message`);
@ -415,21 +449,21 @@ function displayYesNoOptions(resolve) {
handler: () => { handler: () => {
onYesAbilitySwap(resolve); onYesAbilitySwap(resolve);
return true; return true;
} },
}, },
{ {
label: i18next.t("menu:no"), label: i18next.t("menu:no"),
handler: () => { handler: () => {
resolve(false); resolve(false);
return true; return true;
} },
} },
]; ];
const config: OptionSelectConfig = { const config: OptionSelectConfig = {
options: fullOptions, options: fullOptions,
maxOptions: 7, maxOptions: 7,
yOffset: 0 yOffset: 0,
}; };
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true); globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
} }
@ -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 // 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 // This is to prevent "over-generating" a random item of a certain type during item swaps
const ultraPool = [ const ultraPool = [
[ modifierTypes.REVIVER_SEED, 1 ], [modifierTypes.REVIVER_SEED, 1],
[ modifierTypes.GOLDEN_PUNCH, 5 ], [modifierTypes.GOLDEN_PUNCH, 5],
[ modifierTypes.ATTACK_TYPE_BOOSTER, 99 ], [modifierTypes.ATTACK_TYPE_BOOSTER, 99],
[ modifierTypes.QUICK_CLAW, 3 ], [modifierTypes.QUICK_CLAW, 3],
[ modifierTypes.WIDE_LENS, 3 ] [modifierTypes.WIDE_LENS, 3],
]; ];
const roguePool = [ const roguePool = [
[ modifierTypes.LEFTOVERS, 4 ], [modifierTypes.LEFTOVERS, 4],
[ modifierTypes.SHELL_BELL, 4 ], [modifierTypes.SHELL_BELL, 4],
[ modifierTypes.SOUL_DEW, 10 ], [modifierTypes.SOUL_DEW, 10],
[ modifierTypes.SCOPE_LENS, 1 ], [modifierTypes.SCOPE_LENS, 1],
[ modifierTypes.BATON, 1 ], [modifierTypes.BATON, 1],
[ modifierTypes.FOCUS_BAND, 5 ], [modifierTypes.FOCUS_BAND, 5],
[ modifierTypes.KINGS_ROCK, 3 ], [modifierTypes.KINGS_ROCK, 3],
[ modifierTypes.GRIP_CLAW, 5 ] [modifierTypes.GRIP_CLAW, 5],
]; ];
const berryPool = [ const berryPool = [
[ BerryType.APICOT, 3 ], [BerryType.APICOT, 3],
[ BerryType.ENIGMA, 2 ], [BerryType.ENIGMA, 2],
[ BerryType.GANLON, 3 ], [BerryType.GANLON, 3],
[ BerryType.LANSAT, 3 ], [BerryType.LANSAT, 3],
[ BerryType.LEPPA, 2 ], [BerryType.LEPPA, 2],
[ BerryType.LIECHI, 3 ], [BerryType.LIECHI, 3],
[ BerryType.LUM, 2 ], [BerryType.LUM, 2],
[ BerryType.PETAYA, 3 ], [BerryType.PETAYA, 3],
[ BerryType.SALAC, 2 ], [BerryType.SALAC, 2],
[ BerryType.SITRUS, 2 ], [BerryType.SITRUS, 2],
[ BerryType.STARF, 3 ] [BerryType.STARF, 3],
]; ];
let pool: any[]; let pool: any[];
@ -506,7 +540,7 @@ function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: Mod
const newItemType = pool[randIndex]; const newItemType = pool[randIndex];
let newMod: PokemonHeldItemModifierType; let newMod: PokemonHeldItemModifierType;
if (tier === "Berries") { if (tier === "Berries") {
newMod = generateModifierType(modifierTypes.BERRY, [ newItemType[0] ]) as PokemonHeldItemModifierType; newMod = generateModifierType(modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType;
} else { } else {
newMod = generateModifierType(newItemType[0]) as PokemonHeldItemModifierType; newMod = generateModifierType(newItemType[0]) as PokemonHeldItemModifierType;
} }

View File

@ -8,8 +8,17 @@ import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-
import { DANCING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { DANCING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
import { catchPokemon, getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
catchPokemon,
getEncounterPokemonLevelForWave,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#app/data/trainer-config";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
@ -44,7 +53,7 @@ const BAILE_STYLE_BIOMES = [
Biome.WASTELAND, Biome.WASTELAND,
Biome.MOUNTAIN, Biome.MOUNTAIN,
Biome.BADLANDS, Biome.BADLANDS,
Biome.DESERT Biome.DESERT,
]; ];
// Electric form // Electric form
@ -55,7 +64,7 @@ const POM_POM_STYLE_BIOMES = [
Biome.LABORATORY, Biome.LABORATORY,
Biome.SLUM, Biome.SLUM,
Biome.METROPOLIS, Biome.METROPOLIS,
Biome.DOJO Biome.DOJO,
]; ];
// Psychic form // Psychic form
@ -66,7 +75,7 @@ const PAU_STYLE_BIOMES = [
Biome.PLAINS, Biome.PLAINS,
Biome.GRASS, Biome.GRASS,
Biome.TALL_GRASS, Biome.TALL_GRASS,
Biome.FOREST Biome.FOREST,
]; ];
// Ghost form // Ghost form
@ -77,7 +86,7 @@ const SENSU_STYLE_BIOMES = [
Biome.ABYSS, Biome.ABYSS,
Biome.GRAVEYARD, Biome.GRAVEYARD,
Biome.LAKE, Biome.LAKE,
Biome.TEMPLE Biome.TEMPLE,
]; ];
/** /**
@ -85,239 +94,259 @@ const SENSU_STYLE_BIOMES = [
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3823 | GitHub Issue #3823} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3823 | GitHub Issue #3823}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const DancingLessonsEncounter: MysteryEncounter = export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DANCING_LESSONS) MysteryEncounterType.DANCING_LESSONS,
.withEncounterTier(MysteryEncounterTier.GREAT) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.GREAT)
.withIntroSpriteConfigs([]) // Uses a real Pokemon sprite instead of ME Intro Visuals .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withAnimations(EncounterAnim.DANCE) .withIntroSpriteConfigs([]) // Uses a real Pokemon sprite instead of ME Intro Visuals
.withHideWildIntroMessage(true) .withAnimations(EncounterAnim.DANCE)
.withAutoHideIntroVisuals(false) .withHideWildIntroMessage(true)
.withCatchAllowed(true) .withAutoHideIntroVisuals(false)
.withFleeAllowed(false) .withCatchAllowed(true)
.withOnVisualsStart(() => { .withFleeAllowed(false)
const oricorio = globalScene.getEnemyPokemon()!; .withOnVisualsStart(() => {
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, globalScene.getPlayerPokemon()!); const oricorio = globalScene.getEnemyPokemon()!;
danceAnim.play(false, () => { const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, globalScene.getPlayerPokemon()!);
if (oricorio.shiny) { danceAnim.play(false, () => {
oricorio.sparkle(); if (oricorio.shiny) {
} oricorio.sparkle();
});
return true;
})
.withIntroDialogue([
{
text: `${namespace}:intro`,
} }
]) });
.setLocalizationKey(`${namespace}`) return true;
.withTitle(`${namespace}:title`) })
.withDescription(`${namespace}:description`) .withIntroDialogue([
.withQuery(`${namespace}:query`) {
.withOnInit(() => { text: `${namespace}:intro`,
const encounter = globalScene.currentBattle.mysteryEncounter!; },
])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const species = getPokemonSpecies(Species.ORICORIO); const species = getPokemonSpecies(Species.ORICORIO);
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const enemyPokemon = new EnemyPokemon(species, level, TrainerSlot.NONE, false); const enemyPokemon = new EnemyPokemon(species, level, TrainerSlot.NONE, false);
if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) { if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) {
if (enemyPokemon.moveset.length < 4) { if (enemyPokemon.moveset.length < 4) {
enemyPokemon.moveset.push(new PokemonMove(Moves.REVELATION_DANCE)); 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;
} else { } else {
enemyPokemon.formIndex = 0; enemyPokemon.moveset[0] = new PokemonMove(Moves.REVELATION_DANCE);
} }
}
const oricorioData = new PokemonData(enemyPokemon); // Set the form index based on the biome
const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData); // 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) const oricorioData = new PokemonData(enemyPokemon);
globalScene.getEnemyParty().forEach(enemyPokemon => { const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
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 = { // Adds a real Pokemon sprite to the field (required for the animation)
pokemonConfigs: [{ 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, species: species,
dataSource: oricorioData, dataSource: oricorioData,
isBoss: true, isBoss: true,
// Gets +1 to all stats except SPD on battle start // 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) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.boss_enraged`); queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF ], 1)); globalScene.unshiftPhase(
new StatStageChangePhase(
pokemon.getBattlerIndex(),
true,
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF],
1,
),
);
},
},
],
};
encounter.enemyPartyConfigs = [config];
encounter.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) {
encounter.enemyPartyConfigs = [ config ]; return getEncounterText(`${namespace}:invalid_selection`) ?? null;
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);
}
} }
await hideOricorioPokemon(); return null;
await catchPokemon(oricorio, null, PokeballType.POKEBALL, false); };
leaveEncounterWithoutBattle(true);
}) return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
.build() })
) .withOptionPhase(async () => {
.build(); // 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() { function hideOricorioPokemon() {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
@ -332,7 +361,7 @@ function hideOricorioPokemon() {
onComplete: () => { onComplete: () => {
globalScene.field.remove(oricorioSprite, true); globalScene.field.remove(oricorioSprite, true);
resolve(); resolve();
} },
}); });
}); });
} }

View File

@ -1,4 +1,4 @@
import type { Type } from "#enums/type"; import type { PokemonType } from "#enums/pokemon-type";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
@ -9,8 +9,11 @@ import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounte
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils"; import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils"; import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle } from "../utils/encounter-phase-utils";
import { getRandomPlayerPokemon, getRandomSpeciesByStarterCost } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import {
getRandomPlayerPokemon,
getRandomSpeciesByStarterCost,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
@ -93,131 +96,132 @@ const excludedBosses = [
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3806 | GitHub Issue #3806} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3806 | GitHub Issue #3806}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const DarkDealEncounter: MysteryEncounter = export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DARK_DEAL) MysteryEncounterType.DARK_DEAL,
.withEncounterTier(MysteryEncounterTier.ROGUE) )
.withIntroSpriteConfigs([ .withEncounterTier(MysteryEncounterTier.ROGUE)
{ .withIntroSpriteConfigs([
spriteKey: "dark_deal_scientist", {
fileRoot: "mystery-encounters", spriteKey: "dark_deal_scientist",
hasShadow: true, fileRoot: "mystery-encounters",
}, hasShadow: true,
{ },
spriteKey: "dark_deal_porygon", {
fileRoot: "mystery-encounters", spriteKey: "dark_deal_porygon",
hasShadow: true, fileRoot: "mystery-encounters",
repeat: true, hasShadow: true,
}, repeat: true,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
{ },
speaker: `${namespace}:speaker`, {
text: `${namespace}:intro_dialogue`, 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 .withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withCatchAllowed(true) .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
.setLocalizationKey(`${namespace}`) .withCatchAllowed(true)
.withTitle(`${namespace}:title`) .setLocalizationKey(`${namespace}`)
.withDescription(`${namespace}:description`) .withTitle(`${namespace}:title`)
.withQuery(`${namespace}:query`) .withDescription(`${namespace}:description`)
.withOption( .withQuery(`${namespace}:query`)
MysteryEncounterOptionBuilder .withOption(
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [
{
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: Type[] = encounter.misc.removedTypes;
const singleTypeChallenges = globalScene.gameMode.challenges.filter(c => c.value && c.id === Challenges.SINGLE_TYPE);
if (globalScene.gameMode.isChallenge && singleTypeChallenges.length > 0) {
bossTypes = singleTypeChallenges.map(c => (c.value - 1) as Type);
}
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: [ selected: [
{ {
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}:option.2.selected`, text: `${namespace}:option.1.selected_dialogue`,
},
{
text: `${namespace}:option.1.selected_message`,
}, },
], ],
}, })
async () => { .withPreOptionPhase(async () => {
// Leave encounter with no rewards or exp // Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
leaveEncounterWithoutBattle(true); // Will never return last battle able mon and instead pick fainted/unable to battle
return true; const removedPokemon = getRandomPlayerPokemon(true, false, true);
}
) // Get all the pokemon's held items
.withOutroDialogue([ const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
{ globalScene.removePokemonFromPlayerParty(removedPokemon);
text: `${namespace}:outro`
} const encounter = globalScene.currentBattle.mysteryEncounter!;
]) encounter.setDialogueToken("pokeName", removedPokemon.getNameToRender());
.build();
// 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();

View File

@ -2,16 +2,31 @@ import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import {
CombinationPokemonRequirement,
HeldItemRequirement,
MoneyRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
generateModifierType,
leaveEncounterWithoutBattle,
selectPokemonForOption,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PreserveBerryModifier } from "#app/modifier/modifier"; import {
BerryModifier,
HealingBoosterModifier,
LevelIncrementBoosterModifier,
MoneyMultiplierModifier,
PreserveBerryModifier,
} from "#app/modifier/modifier";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
@ -27,7 +42,7 @@ import { Species } from "#enums/species";
const namespace = "mysteryEncounters/delibirdy"; const namespace = "mysteryEncounters/delibirdy";
/** Berries only */ /** 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) */ /** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */
const OPTION_3_DISALLOWED_MODIFIERS = [ const OPTION_3_DISALLOWED_MODIFIERS = [
@ -35,7 +50,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [
"PokemonInstantReviveModifier", "PokemonInstantReviveModifier",
"TerastallizeModifier", "TerastallizeModifier",
"PokemonBaseStatModifier", "PokemonBaseStatModifier",
"PokemonBaseStatTotalModifier" "PokemonBaseStatTotalModifier",
]; ];
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2; const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
@ -43,11 +58,11 @@ const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
const doEventReward = () => { const doEventReward = () => {
const event_buff = globalScene.eventManager.getDelibirdyBuff(); const event_buff = globalScene.eventManager.getDelibirdyBuff();
if (event_buff.length > 0) { if (event_buff.length > 0) {
const candidates = event_buff.filter((c => { const candidates = event_buff.filter(c => {
const mtype = generateModifierType(modifierTypes[c]); const mtype = generateModifierType(modifierTypes[c]);
const existingCharm = globalScene.findModifier(m => m.type.id === mtype?.id); const existingCharm = globalScene.findModifier(m => m.type.id === mtype?.id);
return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount()); return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount());
})); });
if (candidates.length > 0) { if (candidates.length > 0) {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes[randSeedItem(candidates)])); globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes[randSeedItem(candidates)]));
} else { } else {
@ -62,282 +77,308 @@ const doEventReward = () => {
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3804 | GitHub Issue #3804} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3804 | GitHub Issue #3804}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const DelibirdyEncounter: MysteryEncounter = export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY) MysteryEncounterType.DELIBIRDY,
.withEncounterTier(MysteryEncounterTier.GREAT) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withPrimaryPokemonRequirement( .withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least
CombinationPokemonRequirement.Some( .withPrimaryPokemonRequirement(
// Must also have either option 2 or 3 available to spawn CombinationPokemonRequirement.Some(
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS), // Must also have either option 2 or 3 available to spawn
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true) new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),
) new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true),
) ),
.withIntroSpriteConfigs([ )
{ .withIntroSpriteConfigs([
spriteKey: "", {
fileRoot: "", spriteKey: "",
species: Species.DELIBIRD, fileRoot: "",
hasShadow: true, species: Species.DELIBIRD,
repeat: true, hasShadow: true,
startFrame: 38, repeat: true,
scale: 0.94 startFrame: 38,
}, scale: 0.94,
{ },
spriteKey: "", {
fileRoot: "", spriteKey: "",
species: Species.DELIBIRD, fileRoot: "",
hasShadow: true, species: Species.DELIBIRD,
repeat: true, hasShadow: true,
scale: 1.06 repeat: true,
}, scale: 1.06,
{ },
spriteKey: "", {
fileRoot: "", spriteKey: "",
species: Species.DELIBIRD, fileRoot: "",
hasShadow: true, species: Species.DELIBIRD,
repeat: true, hasShadow: true,
startFrame: 65, repeat: true,
x: 1, startFrame: 65,
y: 5, x: 1,
yShadow: 5 y: 5,
}, yShadow: 5,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
} text: `${namespace}:intro`,
]) },
.setLocalizationKey(`${namespace}`) ])
.withTitle(`${namespace}:title`) .setLocalizationKey(`${namespace}`)
.withDescription(`${namespace}:description`) .withTitle(`${namespace}:title`)
.withQuery(`${namespace}:query`) .withDescription(`${namespace}:description`)
.withOutroDialogue([ .withQuery(`${namespace}:query`)
{ .withOutroDialogue([
text: `${namespace}:outro`, {
} text: `${namespace}:outro`,
]) },
.withOnInit(() => { ])
const encounter = globalScene.currentBattle.mysteryEncounter!; .withOnInit(() => {
encounter.setDialogueToken("delibirdName", getPokemonSpecies(Species.DELIBIRD).getName()); const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.setDialogueToken("delibirdName", getPokemonSpecies(Species.DELIBIRD).getName());
globalScene.loadBgm("mystery_encounter_delibirdy", "mystery_encounter_delibirdy.mp3"); globalScene.loadBgm("mystery_encounter_delibirdy", "mystery_encounter_delibirdy.mp3");
return true; return true;
}) })
.withOnVisualsStart(() => { .withOnVisualsStart(() => {
globalScene.fadeAndSwitchBgm("mystery_encounter_delibirdy"); globalScene.fadeAndSwitchBgm("mystery_encounter_delibirdy");
return true; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .withSceneMoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER) // Must have money to spawn
.withSceneMoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER) // Must have money to spawn .withDialogue({
.withDialogue({ buttonLabel: `${namespace}:option.1.label`,
buttonLabel: `${namespace}:option.1.label`, buttonTooltip: `${namespace}:option.1.tooltip`,
buttonTooltip: `${namespace}:option.1.tooltip`, selected: [
selected: [ {
{ text: `${namespace}:option.1.selected`,
text: `${namespace}:option.1.selected`, },
}, ],
], })
}) .withPreOptionPhase(async (): Promise<boolean> => {
.withPreOptionPhase(async (): Promise<boolean> => { const encounter = globalScene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!; updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false);
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false); return true;
return true; })
}) .withOptionPhase(async () => {
.withOptionPhase(async () => { // Give the player an Amulet Coin
// Give the player an Amulet Coin // Check if the player has max stacks of that item already
// Check if the player has max stacks of that item already const existing = globalScene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier;
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()) { if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
// At max stacks, give the first party pokemon a Shell Bell instead // At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell); await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
globalScene.playSound("item_fanfare"); globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); await showEncounterText(
i18next.t("battle:rewardGain", {
modifierName: shellBell.name,
}),
null,
undefined,
true,
);
doEventReward(); doEventReward();
} else { } else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.AMULET_COIN)); globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.CANDY_JAR));
doEventReward(); doEventReward();
} }
} else {
leaveEncounterWithoutBattle(true); // Check if the player has max stacks of that Berry Pouch already
}) const existing = globalScene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
.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;
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
// At max stacks, give the first party pokemon a Shell Bell instead // At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerParty()[0], shellBell); await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
globalScene.playSound("item_fanfare"); globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); await showEncounterText(
i18next.t("battle:rewardGain", {
modifierName: shellBell.name,
}),
null,
undefined,
true,
);
doEventReward(); doEventReward();
} else { } else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.HEALING_CHARM)); globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.BERRY_POUCH));
doEventReward(); doEventReward();
} }
}
chosenPokemon.loseHeldItem(modifier, false); chosenPokemon.loseHeldItem(modifier, false);
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build() .build(),
) )
.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();

View File

@ -8,9 +8,7 @@ import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
MysteryEncounterBuilder,
} from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
@ -22,145 +20,158 @@ const namespace = "mysteryEncounters/departmentStoreSale";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3797 | GitHub Issue #3797} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3797 | GitHub Issue #3797}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const DepartmentStoreSaleEncounter: MysteryEncounter = export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE) MysteryEncounterType.DEPARTMENT_STORE_SALE,
.withEncounterTier(MysteryEncounterTier.COMMON) )
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100) .withEncounterTier(MysteryEncounterTier.COMMON)
.withIntroSpriteConfigs([ .withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
{ .withIntroSpriteConfigs([
spriteKey: "department_store_sale_lady", {
fileRoot: "mystery-encounters", spriteKey: "department_store_sale_lady",
hasShadow: true, fileRoot: "mystery-encounters",
x: -20, hasShadow: true,
}, x: -20,
{ },
spriteKey: "", {
fileRoot: "", spriteKey: "",
species: Species.FURFROU, fileRoot: "",
hasShadow: true, species: Species.FURFROU,
repeat: true, hasShadow: true,
x: 30, repeat: true,
}, x: 30,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
{ },
text: `${namespace}:intro_dialogue`, {
speaker: `${namespace}:speaker`, text: `${namespace}:intro_dialogue`,
}, speaker: `${namespace}:speaker`,
]) },
.withAutoHideIntroVisuals(false) ])
.setLocalizationKey(`${namespace}`) .withAutoHideIntroVisuals(false)
.withTitle(`${namespace}:title`) .setLocalizationKey(`${namespace}`)
.withDescription(`${namespace}:description`) .withTitle(`${namespace}:title`)
.withQuery(`${namespace}:query`) .withDescription(`${namespace}:description`)
.withSimpleOption( .withQuery(`${namespace}:query`)
{ .withSimpleOption(
buttonLabel: `${namespace}:option.1.label`, {
buttonTooltip: `${namespace}:option.1.tooltip`, buttonLabel: `${namespace}:option.1.label`,
}, buttonTooltip: `${namespace}:option.1.tooltip`,
async () => { },
// Choose TMs async () => {
const modifiers: ModifierTypeFunc[] = []; // Choose TMs
let i = 0; const modifiers: ModifierTypeFunc[] = [];
while (i < 5) { let i = 0;
// 2/2/1 weight on TM rarity while (i < 5) {
const roll = randSeedInt(5); // 2/2/1 weight on TM rarity
if (roll < 2) { const roll = randSeedInt(5);
modifiers.push(modifierTypes.TM_COMMON); if (roll < 2) {
} else if (roll < 4) { modifiers.push(modifierTypes.TM_COMMON);
modifiers.push(modifierTypes.TM_GREAT); } else if (roll < 4) {
} else { modifiers.push(modifierTypes.TM_GREAT);
modifiers.push(modifierTypes.TM_ULTRA); } else {
} modifiers.push(modifierTypes.TM_ULTRA);
i++;
} }
i++;
setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, });
leaveEncounterWithoutBattle();
} }
)
.withSimpleOption( setEncounterRewards({
{ guaranteedModifierTypeFuncs: modifiers,
buttonLabel: `${namespace}:option.2.label`, fillRemaining: false,
buttonTooltip: `${namespace}:option.2.tooltip`, });
}, leaveEncounterWithoutBattle();
async () => { },
// Choose Vitamins )
const modifiers: ModifierTypeFunc[] = []; .withSimpleOption(
let i = 0; {
while (i < 3) { buttonLabel: `${namespace}:option.2.label`,
// 2/1 weight on base stat booster vs PP Up buttonTooltip: `${namespace}:option.2.tooltip`,
const roll = randSeedInt(3); },
if (roll === 0) { async () => {
modifiers.push(modifierTypes.PP_UP); // Choose Vitamins
} else { const modifiers: ModifierTypeFunc[] = [];
modifiers.push(modifierTypes.BASE_STAT_BOOSTER); let i = 0;
} while (i < 3) {
i++; // 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( setEncounterRewards({
{ guaranteedModifierTypeFuncs: modifiers,
buttonLabel: `${namespace}:option.3.label`, fillRemaining: false,
buttonTooltip: `${namespace}:option.3.tooltip`, });
}, leaveEncounterWithoutBattle();
async () => { },
// Choose X Items )
const modifiers: ModifierTypeFunc[] = []; .withSimpleOption(
let i = 0; {
while (i < 5) { buttonLabel: `${namespace}:option.3.label`,
// 4/1 weight on base stat booster vs Dire Hit buttonTooltip: `${namespace}:option.3.tooltip`,
const roll = randSeedInt(5); },
if (roll === 0) { async () => {
modifiers.push(modifierTypes.DIRE_HIT); // Choose X Items
} else { const modifiers: ModifierTypeFunc[] = [];
modifiers.push(modifierTypes.TEMP_STAT_STAGE_BOOSTER); let i = 0;
} while (i < 5) {
i++; // 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( setEncounterRewards({
{ guaranteedModifierTypeFuncs: modifiers,
buttonLabel: `${namespace}:option.4.label`, fillRemaining: false,
buttonTooltip: `${namespace}:option.4.tooltip`, });
}, leaveEncounterWithoutBattle();
async () => { },
// Choose Pokeballs )
const modifiers: ModifierTypeFunc[] = []; .withSimpleOption(
let i = 0; {
while (i < 4) { buttonLabel: `${namespace}:option.4.label`,
// 10/30/20/5 weight on pokeballs buttonTooltip: `${namespace}:option.4.tooltip`,
const roll = randSeedInt(65); },
if (roll < 10) { async () => {
modifiers.push(modifierTypes.POKEBALL); // Choose Pokeballs
} else if (roll < 40) { const modifiers: ModifierTypeFunc[] = [];
modifiers.push(modifierTypes.GREAT_BALL); let i = 0;
} else if (roll < 60) { while (i < 4) {
modifiers.push(modifierTypes.ULTRA_BALL); // 10/30/20/5 weight on pokeballs
} else { const roll = randSeedInt(65);
modifiers.push(modifierTypes.ROGUE_BALL); if (roll < 10) {
} modifiers.push(modifierTypes.POKEBALL);
i++; } 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, }); setEncounterRewards({
leaveEncounterWithoutBattle(); guaranteedModifierTypeFuncs: modifiers,
} fillRemaining: false,
) });
.withOutroDialogue([ leaveEncounterWithoutBattle();
{ },
text: `${namespace}:outro`, )
} .withOutroDialogue([
]) {
.build(); text: `${namespace}:outro`,
},
])
.build();

View File

@ -1,6 +1,12 @@
import { MoveCategory } from "#app/data/move"; import { MoveCategory } from "#enums/MoveCategory";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
generateModifierTypeOption,
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterExp,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
@ -22,180 +28,187 @@ const namespace = "mysteryEncounters/fieldTrip";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3794 | GitHub Issue #3794} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3794 | GitHub Issue #3794}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const FieldTripEncounter: MysteryEncounter = export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP) MysteryEncounterType.FIELD_TRIP,
.withEncounterTier(MysteryEncounterTier.COMMON) )
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100) .withEncounterTier(MysteryEncounterTier.COMMON)
.withIntroSpriteConfigs([ .withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
{ .withIntroSpriteConfigs([
spriteKey: "preschooler_m", {
fileRoot: "trainer", spriteKey: "preschooler_m",
hasShadow: true, fileRoot: "trainer",
}, hasShadow: true,
{ },
spriteKey: "field_trip_teacher", {
fileRoot: "mystery-encounters", spriteKey: "field_trip_teacher",
hasShadow: true, fileRoot: "mystery-encounters",
}, hasShadow: true,
{ },
spriteKey: "preschooler_f", {
fileRoot: "trainer", spriteKey: "preschooler_f",
hasShadow: true, fileRoot: "trainer",
}, hasShadow: true,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
{ },
text: `${namespace}:intro_dialogue`, {
speaker: `${namespace}:speaker`, text: `${namespace}:intro_dialogue`,
}, speaker: `${namespace}:speaker`,
]) },
.withAutoHideIntroVisuals(false) ])
.setLocalizationKey(`${namespace}`) .withAutoHideIntroVisuals(false)
.withTitle(`${namespace}:title`) .setLocalizationKey(`${namespace}`)
.withDescription(`${namespace}:description`) .withTitle(`${namespace}:title`)
.withQuery(`${namespace}:query`) .withDescription(`${namespace}:description`)
.withOption( .withQuery(`${namespace}:query`)
MysteryEncounterOptionBuilder .withOption(
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
secondOptionPrompt: `${namespace}:second_option_prompt`, secondOptionPrompt: `${namespace}:second_option_prompt`,
}) })
.withPreOptionPhase(async (): Promise<boolean> => { .withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => { const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for Pokemon move valid for this option // Return the options for Pokemon move valid for this option
return pokemon.moveset.map((move: PokemonMove) => { return pokemon.moveset.map((move: PokemonMove) => {
const option: OptionSelectItem = { const option: OptionSelectItem = {
label: move.getName(), label: move.getName(),
handler: () => { handler: () => {
// Pokemon and move selected // Pokemon and move selected
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:physical`)); encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:physical`));
pokemonAndMoveChosen(pokemon, move, MoveCategory.PHYSICAL); pokemonAndMoveChosen(pokemon, move, MoveCategory.PHYSICAL);
return true; return true;
}, },
}; };
return option; return option;
}); });
}; };
return selectPokemonForOption(onPokemonSelected); return selectPokemonForOption(onPokemonSelected);
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
if (encounter.misc.correctMove) { if (encounter.misc.correctMove) {
const modifiers = [ const modifiers = [
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ATK ])!, generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.ATK])!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.DEF ])!, generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.DEF])!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!, generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
generateModifierTypeOption(modifierTypes.DIRE_HIT)!, generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
generateModifierTypeOption(modifierTypes.RARER_CANDY)!, generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
]; ];
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); setEncounterRewards({
} guaranteedModifierTypeOptions: modifiers,
fillRemaining: false,
});
}
leaveEncounterWithoutBattle(!encounter.misc.correctMove); leaveEncounterWithoutBattle(!encounter.misc.correctMove);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .withDialogue({
.withDialogue({ buttonLabel: `${namespace}:option.2.label`,
buttonLabel: `${namespace}:option.2.label`, buttonTooltip: `${namespace}:option.2.tooltip`,
buttonTooltip: `${namespace}:option.2.tooltip`, secondOptionPrompt: `${namespace}:second_option_prompt`,
secondOptionPrompt: `${namespace}:second_option_prompt`, })
}) .withPreOptionPhase(async (): Promise<boolean> => {
.withPreOptionPhase(async (): Promise<boolean> => { const encounter = globalScene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => {
const onPokemonSelected = (pokemon: PlayerPokemon) => { // Return the options for Pokemon move valid for this option
// Return the options for Pokemon move valid for this option return pokemon.moveset.map((move: PokemonMove) => {
return pokemon.moveset.map((move: PokemonMove) => { const option: OptionSelectItem = {
const option: OptionSelectItem = { label: move.getName(),
label: move.getName(), handler: () => {
handler: () => { // Pokemon and move selected
// Pokemon and move selected encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:special`));
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:special`)); pokemonAndMoveChosen(pokemon, move, MoveCategory.SPECIAL);
pokemonAndMoveChosen(pokemon, move, MoveCategory.SPECIAL); return true;
return true; },
}, };
}; return option;
return option; });
}); };
};
return selectPokemonForOption(onPokemonSelected); return selectPokemonForOption(onPokemonSelected);
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
if (encounter.misc.correctMove) { if (encounter.misc.correctMove) {
const modifiers = [ const modifiers = [
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPATK ])!, generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPATK])!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPDEF ])!, generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPDEF])!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!, generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
generateModifierTypeOption(modifierTypes.DIRE_HIT)!, generateModifierTypeOption(modifierTypes.DIRE_HIT)!,
generateModifierTypeOption(modifierTypes.RARER_CANDY)!, generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
]; ];
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); setEncounterRewards({
} guaranteedModifierTypeOptions: modifiers,
fillRemaining: false,
});
}
leaveEncounterWithoutBattle(!encounter.misc.correctMove); leaveEncounterWithoutBattle(!encounter.misc.correctMove);
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .withDialogue({
.withDialogue({ buttonLabel: `${namespace}:option.3.label`,
buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`,
buttonTooltip: `${namespace}:option.3.tooltip`, secondOptionPrompt: `${namespace}:second_option_prompt`,
secondOptionPrompt: `${namespace}:second_option_prompt`, })
}) .withPreOptionPhase(async (): Promise<boolean> => {
.withPreOptionPhase(async (): Promise<boolean> => { const encounter = globalScene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => {
const onPokemonSelected = (pokemon: PlayerPokemon) => { // Return the options for Pokemon move valid for this option
// Return the options for Pokemon move valid for this option return pokemon.moveset.map((move: PokemonMove) => {
return pokemon.moveset.map((move: PokemonMove) => { const option: OptionSelectItem = {
const option: OptionSelectItem = { label: move.getName(),
label: move.getName(), handler: () => {
handler: () => { // Pokemon and move selected
// Pokemon and move selected encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:status`));
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:status`)); pokemonAndMoveChosen(pokemon, move, MoveCategory.STATUS);
pokemonAndMoveChosen(pokemon, move, MoveCategory.STATUS); return true;
return true; },
}, };
}; return option;
return option; });
}); };
};
return selectPokemonForOption(onPokemonSelected); return selectPokemonForOption(onPokemonSelected);
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
if (encounter.misc.correctMove) { if (encounter.misc.correctMove) {
const modifiers = [ const modifiers = [
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ACC ])!, generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.ACC])!,
generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!, generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
generateModifierTypeOption(modifierTypes.GREAT_BALL)!, generateModifierTypeOption(modifierTypes.GREAT_BALL)!,
generateModifierTypeOption(modifierTypes.IV_SCANNER)!, generateModifierTypeOption(modifierTypes.IV_SCANNER)!,
generateModifierTypeOption(modifierTypes.RARER_CANDY)!, generateModifierTypeOption(modifierTypes.RARER_CANDY)!,
]; ];
setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); setEncounterRewards({
} guaranteedModifierTypeOptions: modifiers,
fillRemaining: false,
});
}
leaveEncounterWithoutBattle(!encounter.misc.correctMove); leaveEncounterWithoutBattle(!encounter.misc.correctMove);
}) })
.build() .build(),
) )
.build(); .build();
function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correctMoveCategory: MoveCategory) { function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correctMoveCategory: MoveCategory) {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -215,7 +228,10 @@ function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correct
text: `${namespace}:incorrect_exp`, text: `${namespace}:incorrect_exp`,
}, },
]; ];
setEncounterExp(globalScene.getPlayerParty().map((p) => p.id), 50); setEncounterExp(
globalScene.getPlayerParty().map(p => p.id),
50,
);
} else { } else {
encounter.selectedOption!.dialogue!.selected = [ encounter.selectedOption!.dialogue!.selected = [
{ {
@ -229,7 +245,7 @@ function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correct
text: `${namespace}:correct_exp`, text: `${namespace}:correct_exp`,
}, },
]; ];
setEncounterExp([ pokemon.id ], 100); setEncounterExp([pokemon.id], 100);
} }
encounter.misc = { encounter.misc = {
correctMove: correctMove, correctMove: correctMove,

View File

@ -1,17 +1,29 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
initBattleWithEnemyConfig,
loadCustomMovesForEncounter,
leaveEncounterWithoutBattle,
setEncounterExp,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
generateModifierType,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { modifierTypes, } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { AbilityRequirement, CombinationPokemonRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import {
AbilityRequirement,
CombinationPokemonRequirement,
TypeRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { Type } from "#enums/type"; import { PokemonType } from "#enums/pokemon-type";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
@ -21,7 +33,11 @@ import { WeatherType } from "#enums/weather-type";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyAbilityOverrideToPokemon, applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import {
applyAbilityOverrideToPokemon,
applyDamageToPokemon,
applyModifierTypeToPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";
@ -48,216 +64,230 @@ const DAMAGE_PERCENTAGE: number = 20;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3814 | GitHub Issue #3814} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3814 | GitHub Issue #3814}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const FieryFalloutEncounter: MysteryEncounter = export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIERY_FALLOUT) MysteryEncounterType.FIERY_FALLOUT,
.withEncounterTier(MysteryEncounterTier.COMMON) )
.withSceneWaveRangeRequirement(40, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withEncounterTier(MysteryEncounterTier.COMMON)
.withCatchAllowed(true) .withSceneWaveRangeRequirement(40, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withIntroSpriteConfigs([]) // Set in onInit() .withCatchAllowed(true)
.withAnimations(EncounterAnim.MAGMA_BG, EncounterAnim.MAGMA_SPOUT) .withIntroSpriteConfigs([]) // Set in onInit()
.withAutoHideIntroVisuals(false) .withAnimations(EncounterAnim.MAGMA_BG, EncounterAnim.MAGMA_SPOUT)
.withFleeAllowed(false) .withAutoHideIntroVisuals(false)
.withIntroDialogue([ .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!; const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem());
// Calculate boss mons encounter.startOfBattleEffects.push(
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 = [
{ {
spriteKey: "", sourceBattlerIndex: BattlerIndex.ENEMY,
fileRoot: "", targets: [BattlerIndex.PLAYER],
species: Species.VOLCARONA, move: new PokemonMove(Moves.FIRE_SPIN),
repeat: true, ignorePp: true,
hidden: true,
hasShadow: true,
x: -20,
startFrame: 20
}, },
{ {
spriteKey: "", sourceBattlerIndex: BattlerIndex.ENEMY_2,
fileRoot: "", targets: [BattlerIndex.PLAYER_2],
species: Species.VOLCARONA, move: new PokemonMove(Moves.FIRE_SPIN),
repeat: true, ignorePp: true,
hidden: true,
hasShadow: true,
x: 20
}, },
]; );
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 for (const pkm of nonFireTypes) {
loadCustomMovesForEncounter([ Moves.FIRE_SPIN, Moves.QUIVER_DANCE ]); 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; // No rewards
}) leaveEncounterWithoutBattle(true);
.withOnVisualsStart(() => { },
// Play animations )
const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon()); .withOption(
background.playWithoutTargets(200, 70, 2, 3); MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon()); .withPrimaryPokemonRequirement(
animation.playWithoutTargets(80, 100, 2); CombinationPokemonRequirement.Some(
globalScene.time.delayedCall(600, () => { new TypeRequirement(PokemonType.FIRE, true, 1),
animation.playWithoutTargets(-20, 100, 2); new AbilityRequirement(FIRE_RESISTANT_ABILITIES, true),
}); ),
globalScene.time.delayedCall(1200, () => { ) // Will set option3PrimaryName dialogue token automatically
animation.playWithoutTargets(140, 150, 2); .withDialogue({
}); buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
return true; disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
})
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withSimpleOption(
{
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.1.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}, })
async () => { .withPreOptionPhase(async () => {
// Pick battle // 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!; const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem()); await transitionMysteryEncounterIntroVisuals();
setEncounterRewards({ fillRemaining: true }, undefined, () => {
giveLeadPokemonAttackTypeBoostItem();
});
encounter.startOfBattleEffects.push( const primary = encounter.options[2].primaryPokemon!;
{
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(Type.FIRE));
for (const pkm of nonFireTypes) { setEncounterExp([primary.id], getPokemonSpecies(Species.VOLCARONA).baseExp * 2);
const percentage = DAMAGE_PERCENTAGE / 100; leaveEncounterWithoutBattle();
const damage = Math.floor(pkm.getMaxHp() * percentage); })
applyDamageToPokemon(pkm, damage); .build(),
} )
.build();
// 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(Type.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();
function giveLeadPokemonAttackTypeBoostItem() { function giveLeadPokemonAttackTypeBoostItem() {
// Give first party pokemon attack type boost item for free at end of battle // 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 // Generate type booster held item, default to Charcoal if item fails to generate
let boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType; let boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType;
if (!boosterModifierType) { if (!boosterModifierType) {
boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType; boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
PokemonType.FIRE,
]) as AttackTypeBoosterModifierType;
} }
applyModifierTypeToPlayerPokemon(leadPokemon, boosterModifierType); applyModifierTypeToPlayerPokemon(leadPokemon, boosterModifierType);

View File

@ -1,18 +1,16 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { import {
getRandomEncounterSpecies, getRandomEncounterSpecies,
initBattleWithEnemyConfig, initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, leaveEncounterWithoutBattle,
setEncounterExp, setEncounterExp,
setEncounterRewards setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import type { import type { ModifierTypeOption } from "#app/modifier/modifier-type";
ModifierTypeOption } from "#app/modifier/modifier-type";
import { import {
getPlayerModifierTypeOptions, getPlayerModifierTypeOptions,
ModifierPoolType, ModifierPoolType,
@ -25,7 +23,11 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { getEncounterPokemonLevelForWave, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import {
getEncounterPokemonLevelForWave,
getSpriteKeysFromPokemon,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -41,152 +43,163 @@ const namespace = "mysteryEncounters/fightOrFlight";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3795 | GitHub Issue #3795} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3795 | GitHub Issue #3795}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const FightOrFlightEncounter: MysteryEncounter = export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT) MysteryEncounterType.FIGHT_OR_FLIGHT,
.withEncounterTier(MysteryEncounterTier.COMMON) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.COMMON)
.withCatchAllowed(true) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withHideWildIntroMessage(true) .withCatchAllowed(true)
.withFleeAllowed(false) .withHideWildIntroMessage(true)
.withIntroSpriteConfigs([]) // Set in onInit() .withFleeAllowed(false)
.withIntroDialogue([ .withIntroSpriteConfigs([]) // Set in onInit()
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
]) },
.withOnInit(() => { ])
const encounter = globalScene.currentBattle.mysteryEncounter!; .withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mon // Calculate boss mon
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossPokemon = getRandomEncounterSpecies(level, true); const bossPokemon = getRandomEncounterSpecies(level, true);
encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender()); encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender());
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
pokemonConfigs: [{ pokemonConfigs: [
{
level: level, level: level,
species: bossPokemon.species, species: bossPokemon.species,
dataSource: new PokemonData(bossPokemon), dataSource: new PokemonData(bossPokemon),
isBoss: true, isBoss: true,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.stat_boost`); queueEncounterMessage(`${namespace}:option.1.stat_boost`);
// Randomly boost 1 stat 2 stages // Randomly boost 1 stat 2 stages
// Cannot boost Spd, Acc, or Evasion // Cannot boost Spd, Acc, or Evasion
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ randSeedInt(4, 1) ], 2)); globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2));
} },
}],
};
encounter.enemyPartyConfigs = [ config ];
// 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
}, },
{ ],
spriteKey: spriteKey, };
fileRoot: fileRoot, encounter.enemyPartyConfigs = [config];
hasShadow: true,
tint: 0.25,
x: -5,
repeat: true,
isPokemon: true,
isShiny: bossPokemon.shiny,
variant: bossPokemon.variant
},
];
return true; // Calculate item
}) // Waves 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER
.setLocalizationKey(`${namespace}`) const tier =
.withTitle(`${namespace}:title`) globalScene.currentBattle.waveIndex > 160
.withDescription(`${namespace}:description`) ? ModifierTier.MASTER
.withQuery(`${namespace}:query`) : globalScene.currentBattle.waveIndex > 120
.withSimpleOption( ? 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`, spriteKey: item.type.iconImage,
buttonTooltip: `${namespace}:option.1.tooltip`, 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: [ selected: [
{ {
text: `${namespace}:option.1.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, })
async () => { .withOptionPhase(async () => {
// Pick battle // Pick steal
// Pokemon will randomly boost 1 stat by 2 stages const encounter = globalScene.currentBattle.mysteryEncounter!;
const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption; const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false }); setEncounterRewards({
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); guaranteedModifierTypeOptions: [item],
} fillRemaining: false,
) });
.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 });
// Use primaryPokemon to execute the thievery // Use primaryPokemon to execute the thievery
const primaryPokemon = encounter.options[1].primaryPokemon!; const primaryPokemon = encounter.options[1].primaryPokemon!;
setEncounterExp(primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp); setEncounterExp(primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}:option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}, },
async () => { async () => {
// Leave encounter with no rewards or exp // Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .build();

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import type { import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { import {
initBattleWithEnemyConfig, initBattleWithEnemyConfig,
setEncounterRewards, setEncounterRewards,
@ -29,195 +28,202 @@ const namespace = "mysteryEncounters/mysteriousChallengers";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3801 | GitHub Issue #3801} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3801 | GitHub Issue #3801}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const MysteriousChallengersEncounter: MysteryEncounter = export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS) MysteryEncounterType.MYSTERIOUS_CHALLENGERS,
.withEncounterTier(MysteryEncounterTier.GREAT) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.GREAT)
.withIntroSpriteConfigs([]) // These are set in onInit() .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroDialogue([ .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!; 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 setEncounterRewards({
const normalTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex); guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM],
const normalConfig = trainerConfigs[normalTrainerType].clone(); fillRemaining: true,
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 // Seed offsets to remove possibility of different trainers having exact same teams
// Number of mons is based off wave: 1-20 is 2, 20-40 is 3, etc. capping at 6 after wave 100 let initBattlePromise: Promise<void>;
let retries = 0; globalScene.executeWithSeedOffset(() => {
let hardTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex); initBattlePromise = initBattleWithEnemyConfig(config);
while (retries < 5 && hardTrainerType === normalTrainerType) { }, globalScene.currentBattle.waveIndex * 10);
// Will try to use a different trainer from the normal trainer type await initBattlePromise!;
hardTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex); },
retries++; )
} .withSimpleOption(
const hardTemplate = new TrainerPartyCompoundTemplate( {
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true), buttonLabel: `${namespace}:option.2.label`,
new TrainerPartyTemplate( buttonTooltip: `${namespace}:option.2.tooltip`,
Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 20), 5), selected: [
PartyMemberStrength.AVERAGE, {
false, text: `${namespace}:option.selected`,
true },
) ],
); },
const hardConfig = trainerConfigs[hardTrainerType].clone(); async () => {
hardConfig.setPartyTemplates(hardTemplate); const encounter = globalScene.currentBattle.mysteryEncounter!;
female = false; // Spawn hard fight
if (hardConfig.hasGenders) { const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
female = !!Utils.randSeedInt(2);
} setEncounterRewards({
const hardSpriteKey = hardConfig.getSpriteKey(female, hardConfig.doubleOnly); guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT],
encounter.enemyPartyConfigs.push({ fillRemaining: true,
trainerConfig: hardConfig,
levelAdditiveModifier: 1,
female: female,
}); });
// Brutal trainer is pulled from pool of boss trainers (gym leaders) for the biome // Seed offsets to remove possibility of different trainers having exact same teams
// They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons let initBattlePromise: Promise<void>;
const brutalTrainerType = globalScene.arena.randomTrainerType( globalScene.executeWithSeedOffset(() => {
globalScene.currentBattle.waveIndex, initBattlePromise = initBattleWithEnemyConfig(config);
true }, globalScene.currentBattle.waveIndex * 100);
); await initBattlePromise!;
const e4Template = trainerPartyTemplates.ELITE_FOUR; },
const brutalConfig = trainerConfigs[brutalTrainerType].clone(); )
brutalConfig.title = trainerConfigs[brutalTrainerType].title; .withSimpleOption(
brutalConfig.setPartyTemplates(e4Template); {
// @ts-ignore buttonLabel: `${namespace}:option.3.label`,
brutalConfig.partyTemplateFunc = null; // Overrides gym leader party template func buttonTooltip: `${namespace}:option.3.tooltip`,
female = false; selected: [
if (brutalConfig.hasGenders) { {
female = !!Utils.randSeedInt(2); text: `${namespace}:option.selected`,
} },
const brutalSpriteKey = brutalConfig.getSpriteKey(female, brutalConfig.doubleOnly); ],
encounter.enemyPartyConfigs.push({ },
trainerConfig: brutalConfig, async () => {
levelAdditiveModifier: 1.5, const encounter = globalScene.currentBattle.mysteryEncounter!;
female: female, // 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 = [ // Seed offsets to remove possibility of different trainers having exact same teams
{ let initBattlePromise: Promise<void>;
spriteKey: normalSpriteKey, globalScene.executeWithSeedOffset(() => {
fileRoot: "trainer", initBattlePromise = initBattleWithEnemyConfig(config);
hasShadow: true, }, globalScene.currentBattle.waveIndex * 1000);
tint: 1, await initBattlePromise!;
}, },
{ )
spriteKey: hardSpriteKey, .withOutroDialogue([
fileRoot: "trainer", {
hasShadow: true, text: `${namespace}:outro`,
tint: 1, },
}, ])
{ .build();
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();

View File

@ -4,8 +4,16 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
getHighestLevelPlayerPokemon,
koPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
@ -32,183 +40,181 @@ const MASTER_REWARDS_PERCENT = 5;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3796 | GitHub Issue #3796} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3796 | GitHub Issue #3796}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const MysteriousChestEncounter: MysteryEncounter = export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST) MysteryEncounterType.MYSTERIOUS_CHEST,
.withEncounterTier(MysteryEncounterTier.COMMON) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.COMMON)
.withScenePartySizeRequirement(2, 6, true) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withAutoHideIntroVisuals(false) .withScenePartySizeRequirement(2, 6, true)
.withCatchAllowed(true) .withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([ .withCatchAllowed(true)
{ .withIntroSpriteConfigs([
spriteKey: "mysterious_chest_blue", {
fileRoot: "mystery-encounters", spriteKey: "mysterious_chest_blue",
hasShadow: true, fileRoot: "mystery-encounters",
y: 8, hasShadow: true,
yShadow: 6, y: 8,
alpha: 1, yShadow: 6,
disableAnimation: true, // Re-enabled after option select alpha: 1,
}, disableAnimation: true, // Re-enabled after option select
{ },
spriteKey: "mysterious_chest_red", {
fileRoot: "mystery-encounters", spriteKey: "mysterious_chest_red",
hasShadow: false, fileRoot: "mystery-encounters",
y: 8, hasShadow: false,
yShadow: 6, y: 8,
alpha: 0, yShadow: 6,
disableAnimation: true, // Re-enabled after option select alpha: 0,
} disableAnimation: true, // Re-enabled after option select
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
} text: `${namespace}:intro`,
]) },
.setLocalizationKey(`${namespace}`) ])
.withTitle(`${namespace}:title`) .setLocalizationKey(`${namespace}`)
.withDescription(`${namespace}:description`) .withTitle(`${namespace}:title`)
.withQuery(`${namespace}:query`) .withDescription(`${namespace}:description`)
.withOnInit(() => { .withQuery(`${namespace}:query`)
const encounter = globalScene.currentBattle.mysteryEncounter!; .withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mon // Calculate boss mon
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
levelAdditiveModifier: 0.5, levelAdditiveModifier: 0.5,
disableSwitch: true, disableSwitch: true,
pokemonConfigs: [ pokemonConfigs: [
{ {
species: getPokemonSpecies(Species.GIMMIGHOUL), species: getPokemonSpecies(Species.GIMMIGHOUL),
formIndex: 0, formIndex: 0,
isBoss: true, isBoss: true,
moveSet: [ Moves.NASTY_PLOT, Moves.SHADOW_BALL, Moves.POWER_GEM, Moves.THIEF ] moveSet: [Moves.NASTY_PLOT, Moves.SHADOW_BALL, Moves.POWER_GEM, Moves.THIEF],
} },
], ],
}; };
encounter.enemyPartyConfigs = [ config ]; encounter.enemyPartyConfigs = [config];
encounter.setDialogueToken("gimmighoulName", getPokemonSpecies(Species.GIMMIGHOUL).getName()); encounter.setDialogueToken("gimmighoulName", getPokemonSpecies(Species.GIMMIGHOUL).getName());
encounter.setDialogueToken("trapPercent", TRAP_PERCENT.toString()); encounter.setDialogueToken("trapPercent", TRAP_PERCENT.toString());
encounter.setDialogueToken("commonPercent", COMMON_REWARDS_PERCENT.toString()); encounter.setDialogueToken("commonPercent", COMMON_REWARDS_PERCENT.toString());
encounter.setDialogueToken("ultraPercent", ULTRA_REWARDS_PERCENT.toString()); encounter.setDialogueToken("ultraPercent", ULTRA_REWARDS_PERCENT.toString());
encounter.setDialogueToken("roguePercent", ROGUE_REWARDS_PERCENT.toString()); encounter.setDialogueToken("roguePercent", ROGUE_REWARDS_PERCENT.toString());
encounter.setDialogueToken("masterPercent", MASTER_REWARDS_PERCENT.toString()); encounter.setDialogueToken("masterPercent", MASTER_REWARDS_PERCENT.toString());
return true; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .withDialogue({
.withDialogue({ buttonLabel: `${namespace}:option.1.label`,
buttonLabel: `${namespace}:option.1.label`, buttonTooltip: `${namespace}:option.1.tooltip`,
buttonTooltip: `${namespace}:option.1.tooltip`,
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`,
selected: [ selected: [
{ {
text: `${namespace}:option.2.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}, })
async () => { .withPreOptionPhase(async () => {
// Leave encounter with no rewards or exp // Play animation
leaveEncounterWithoutBattle(true); const encounter = globalScene.currentBattle.mysteryEncounter!;
return true; const introVisuals = encounter.introVisuals!;
}
) // Determine roll first
.build(); 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();

View File

@ -1,5 +1,12 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterExp,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -24,67 +31,68 @@ const namespace = "mysteryEncounters/partTimer";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3813 | GitHub Issue #3813} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3813 | GitHub Issue #3813}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const PartTimerEncounter: MysteryEncounter = export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.PART_TIMER) MysteryEncounterType.PART_TIMER,
.withEncounterTier(MysteryEncounterTier.COMMON) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.COMMON)
.withIntroSpriteConfigs([ .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
{ .withIntroSpriteConfigs([
spriteKey: "part_timer_crate", {
fileRoot: "mystery-encounters", spriteKey: "part_timer_crate",
hasShadow: false, fileRoot: "mystery-encounters",
y: 6, hasShadow: false,
x: 15 y: 6,
}, x: 15,
{ },
spriteKey: "worker_f", {
fileRoot: "trainer", spriteKey: "worker_f",
hasShadow: true, fileRoot: "trainer",
x: -18, hasShadow: true,
y: 4 x: -18,
} y: 4,
]) },
.withAutoHideIntroVisuals(false) ])
.withIntroDialogue([ .withAutoHideIntroVisuals(false)
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
{ },
speaker: `${namespace}:speaker`, {
text: `${namespace}:intro_dialogue`, speaker: `${namespace}:speaker`,
}, text: `${namespace}:intro_dialogue`,
]) },
.withOnInit(() => { ])
// Load sfx .withOnInit(() => {
globalScene.loadSe("PRSFX- Horn Drill1", "battle_anims", "PRSFX- Horn Drill1.wav"); // Load sfx
globalScene.loadSe("PRSFX- Horn Drill3", "battle_anims", "PRSFX- Horn Drill3.wav"); globalScene.loadSe("PRSFX- Horn Drill1", "battle_anims", "PRSFX- Horn Drill1.wav");
globalScene.loadSe("PRSFX- Guillotine2", "battle_anims", "PRSFX- Guillotine2.wav"); globalScene.loadSe("PRSFX- Horn Drill3", "battle_anims", "PRSFX- Horn Drill3.wav");
globalScene.loadSe("PRSFX- Heavy Slam2", "battle_anims", "PRSFX- Heavy Slam2.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- Agility", "battle_anims", "PRSFX- Agility.wav");
globalScene.loadSe("PRSFX- Extremespeed1", "battle_anims", "PRSFX- Extremespeed1.wav"); globalScene.loadSe("PRSFX- Extremespeed1", "battle_anims", "PRSFX- Extremespeed1.wav");
globalScene.loadSe("PRSFX- Accelerock1", "battle_anims", "PRSFX- Accelerock1.wav"); globalScene.loadSe("PRSFX- Accelerock1", "battle_anims", "PRSFX- Accelerock1.wav");
globalScene.loadSe("PRSFX- Captivate", "battle_anims", "PRSFX- Captivate.wav"); globalScene.loadSe("PRSFX- Captivate", "battle_anims", "PRSFX- Captivate.wav");
globalScene.loadSe("PRSFX- Attract2", "battle_anims", "PRSFX- Attract2.wav"); globalScene.loadSe("PRSFX- Attract2", "battle_anims", "PRSFX- Attract2.wav");
globalScene.loadSe("PRSFX- Aurora Veil2", "battle_anims", "PRSFX- Aurora Veil2.wav"); globalScene.loadSe("PRSFX- Aurora Veil2", "battle_anims", "PRSFX- Aurora Veil2.wav");
return true; return true;
}) })
.setLocalizationKey(`${namespace}`) .setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withOption(MysteryEncounterOptionBuilder .withOption(
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.1.selected` text: `${namespace}:option.1.selected`,
} },
] ],
}) })
.withPreOptionPhase(async () => { .withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -95,21 +103,21 @@ export const PartTimerEncounter: MysteryEncounter =
// Calculate the "baseline" stat value (90 base stat, 16 IVs, neutral nature, same level as pokemon) to compare // Calculate the "baseline" stat value (90 base stat, 16 IVs, neutral nature, same level as pokemon) to compare
// Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4. // Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
// Calculation from Pokemon.calculateStats // Calculation from Pokemon.calculateStats
const baselineValue = Math.floor(((2 * 90 + 16) * pokemon.level) * 0.01) + 5; const baselineValue = Math.floor((2 * 90 + 16) * pokemon.level * 0.01) + 5;
const percentDiff = (pokemon.getStat(Stat.SPD) - baselineValue) / baselineValue; const percentDiff = (pokemon.getStat(Stat.SPD) - baselineValue) / baselineValue;
const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4); const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4);
encounter.misc = { encounter.misc = {
moneyMultiplier moneyMultiplier,
}; };
// Reduce all PP to 2 (if they started at greater than 2) // Reduce all PP to 2 (if they started at greater than 2)
pokemon.moveset.forEach(move => { for (const move of pokemon.moveset) {
if (move) { if (move) {
const newPpUsed = move.getMovePp() - 2; const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed; move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
} }
}); }
setEncounterExp(pokemon.id, 100); setEncounterExp(pokemon.id, 100);
@ -141,24 +149,28 @@ export const PartTimerEncounter: MysteryEncounter =
} }
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier); const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(moneyChange, true, false); updatePlayerMoney(moneyChange, true, false);
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); await showEncounterText(
i18next.t("mysteryEncounterMessages:receive_money", {
amount: moneyChange,
}),
);
await showEncounterText(`${namespace}:pokemon_tired`); await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards({ fillRemaining: true }); setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}) })
.build() .build(),
) )
.withOption(MysteryEncounterOptionBuilder .withOption(
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.2.selected` text: `${namespace}:option.2.selected`,
} },
] ],
}) })
.withPreOptionPhase(async () => { .withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -169,24 +181,25 @@ export const PartTimerEncounter: MysteryEncounter =
// Calculate the "baseline" stat value (75 base stat, 16 IVs, neutral nature, same level as pokemon) to compare // Calculate the "baseline" stat value (75 base stat, 16 IVs, neutral nature, same level as pokemon) to compare
// Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4. // Resulting money is 2.5 * (% difference from baseline), with minimum of 1 and maximum of 4.
// Calculation from Pokemon.calculateStats // Calculation from Pokemon.calculateStats
const baselineHp = Math.floor(((2 * 75 + 16) * pokemon.level) * 0.01) + pokemon.level + 10; const baselineHp = Math.floor((2 * 75 + 16) * pokemon.level * 0.01) + pokemon.level + 10;
const baselineAtkDef = Math.floor(((2 * 75 + 16) * pokemon.level) * 0.01) + 5; const baselineAtkDef = Math.floor((2 * 75 + 16) * pokemon.level * 0.01) + 5;
const baselineValue = baselineHp + 1.5 * (baselineAtkDef * 2); const baselineValue = baselineHp + 1.5 * (baselineAtkDef * 2);
const strongestValue = pokemon.getStat(Stat.HP) + 1.5 * (pokemon.getStat(Stat.ATK) + pokemon.getStat(Stat.DEF)); const strongestValue =
pokemon.getStat(Stat.HP) + 1.5 * (pokemon.getStat(Stat.ATK) + pokemon.getStat(Stat.DEF));
const percentDiff = (strongestValue - baselineValue) / baselineValue; const percentDiff = (strongestValue - baselineValue) / baselineValue;
const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4); const moneyMultiplier = Math.min(Math.max(2.5 * (1 + percentDiff), 1), 4);
encounter.misc = { encounter.misc = {
moneyMultiplier moneyMultiplier,
}; };
// Reduce all PP to 2 (if they started at greater than 2) // Reduce all PP to 2 (if they started at greater than 2)
pokemon.moveset.forEach(move => { for (const move of pokemon.moveset) {
if (move) { if (move) {
const newPpUsed = move.getMovePp() - 2; const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed; move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
} }
}); }
setEncounterExp(pokemon.id, 100); setEncounterExp(pokemon.id, 100);
@ -218,73 +231,80 @@ export const PartTimerEncounter: MysteryEncounter =
} }
const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier); const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(moneyChange, true, false); updatePlayerMoney(moneyChange, true, false);
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); await showEncounterText(
i18next.t("mysteryEncounterMessages:receive_money", {
amount: moneyChange,
}),
);
await showEncounterText(`${namespace}:pokemon_tired`); await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards({ fillRemaining: true }); setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}) })
.build() .build(),
) )
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withDialogue({
.withDialogue({ buttonLabel: `${namespace}:option.3.label`,
buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`,
buttonTooltip: `${namespace}:option.3.tooltip`, disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`, selected: [
selected: [ {
{ text: `${namespace}:option.3.selected`,
text: `${namespace}:option.3.selected`, },
}, ],
], })
}) .withPreOptionPhase(async () => {
.withPreOptionPhase(async () => { const encounter = globalScene.currentBattle.mysteryEncounter!;
const encounter = globalScene.currentBattle.mysteryEncounter!; const selectedPokemon = encounter.selectedOption?.primaryPokemon!;
const selectedPokemon = encounter.selectedOption?.primaryPokemon!; encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender());
encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender());
// Reduce all PP to 2 (if they started at greater than 2) // Reduce all PP to 2 (if they started at greater than 2)
selectedPokemon.moveset.forEach(move => { for (const move of selectedPokemon.moveset) {
if (move) { if (move) {
const newPpUsed = move.getMovePp() - 2; const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed; move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
} }
}); }
setEncounterExp(selectedPokemon.id, 100); setEncounterExp(selectedPokemon.id, 100);
// Hide intro visuals // Hide intro visuals
transitionMysteryEncounterIntroVisuals(true, false); transitionMysteryEncounterIntroVisuals(true, false);
// Play sfx for "working" // Play sfx for "working"
doSalesSfx(); doSalesSfx();
return true; return true;
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
// Assist with Sales // Assist with Sales
// Bring visuals back in // Bring visuals back in
await transitionMysteryEncounterIntroVisuals(false, false); await transitionMysteryEncounterIntroVisuals(false, false);
// Give money and do dialogue // Give money and do dialogue
await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`); await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`);
const moneyChange = globalScene.getWaveMoneyAmount(2.5); const moneyChange = globalScene.getWaveMoneyAmount(2.5);
updatePlayerMoney(moneyChange, true, false); updatePlayerMoney(moneyChange, true, false);
await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); await showEncounterText(
await showEncounterText(`${namespace}:pokemon_tired`); i18next.t("mysteryEncounterMessages:receive_money", {
amount: moneyChange,
}),
);
await showEncounterText(`${namespace}:pokemon_tired`);
setEncounterRewards({ fillRemaining: true }); setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}) })
.build() .build(),
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}:outro`, text: `${namespace}:outro`,
} },
]) ])
.build(); .build();
function doStrongWorkSfx() { function doStrongWorkSfx() {
globalScene.playSound("battle_anims/PRSFX- Horn Drill1"); globalScene.playSound("battle_anims/PRSFX- Horn Drill1");

View File

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

View File

@ -1,4 +1,10 @@
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
generateModifierType,
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterExp,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
@ -11,7 +17,11 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon, isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import {
applyDamageToPokemon,
applyModifierTypeToPlayerPokemon,
isPokemonValidForEncounterOptionSelection,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type { Nature } from "#enums/nature"; import type { Nature } from "#enums/nature";
@ -30,201 +40,204 @@ const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 5;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3798 | GitHub Issue #3798} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3798 | GitHub Issue #3798}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ShadyVitaminDealerEncounter: MysteryEncounter = export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER) MysteryEncounterType.SHADY_VITAMIN_DEALER,
.withEncounterTier(MysteryEncounterTier.COMMON) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneRequirement(new MoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)) // Must have the money for at least the cheap deal .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withPrimaryPokemonHealthRatioRequirement([ 0.51, 1 ]) // At least 1 Pokemon must have above half HP .withSceneRequirement(new MoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)) // Must have the money for at least the cheap deal
.withIntroSpriteConfigs([ .withPrimaryPokemonHealthRatioRequirement([0.51, 1]) // At least 1 Pokemon must have above half HP
{ .withIntroSpriteConfigs([
spriteKey: Species.KROOKODILE.toString(), {
fileRoot: "pokemon", spriteKey: Species.KROOKODILE.toString(),
hasShadow: true, fileRoot: "pokemon",
repeat: true, hasShadow: true,
x: 12, repeat: true,
y: -5, x: 12,
yShadow: -5 y: -5,
}, yShadow: -5,
{ },
spriteKey: "shady_vitamin_dealer", {
fileRoot: "mystery-encounters", spriteKey: "shady_vitamin_dealer",
hasShadow: true, fileRoot: "mystery-encounters",
x: -12, hasShadow: true,
y: 3, x: -12,
yShadow: 3 y: 3,
}, yShadow: 3,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
{ },
text: `${namespace}:intro_dialogue`, {
speaker: `${namespace}:speaker`, text: `${namespace}:intro_dialogue`,
}, speaker: `${namespace}:speaker`,
]) },
.setLocalizationKey(`${namespace}`) ])
.withTitle(`${namespace}:title`) .setLocalizationKey(`${namespace}`)
.withDescription(`${namespace}:description`) .withTitle(`${namespace}:title`)
.withQuery(`${namespace}:query`) .withDescription(`${namespace}:description`)
.withOption( .withQuery(`${namespace}:query`)
MysteryEncounterOptionBuilder .withOption(
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER) .withSceneMoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, 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`,
selected: [ selected: [
{ {
text: `${namespace}:option.3.selected`, text: `${namespace}:option.selected`,
speaker: `${namespace}:speaker` },
],
})
.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;
async () => { }
// Leave encounter with no rewards or exp
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); leaveEncounterWithoutBattle(true);
return true; })
} .withPostOptionPhase(async () => {
) // Damage and status applied after dealer leaves (to make thematic sense)
.build(); 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();

View File

@ -10,7 +10,14 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils"; import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils"; import {
generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
loadCustomMovesForEncounter,
setEncounterExp,
setEncounterRewards,
} from "../utils/encounter-phase-utils";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
@ -31,141 +38,148 @@ const namespace = "mysteryEncounters/slumberingSnorlax";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3815 | GitHub Issue #3815} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3815 | GitHub Issue #3815}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const SlumberingSnorlaxEncounter: MysteryEncounter = export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX) MysteryEncounterType.SLUMBERING_SNORLAX,
.withEncounterTier(MysteryEncounterTier.GREAT) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.GREAT)
.withCatchAllowed(true) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withHideWildIntroMessage(true) .withCatchAllowed(true)
.withFleeAllowed(false) .withHideWildIntroMessage(true)
.withIntroSpriteConfigs([ .withFleeAllowed(false)
{ .withIntroSpriteConfigs([
spriteKey: Species.SNORLAX.toString(), {
fileRoot: "pokemon", spriteKey: Species.SNORLAX.toString(),
hasShadow: true, fileRoot: "pokemon",
tint: 0.25, hasShadow: true,
scale: 1.25, tint: 0.25,
repeat: true, scale: 1.25,
y: 5, repeat: true,
}, y: 5,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
]) },
.withOnInit(() => { ])
.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!; const encounter = globalScene.currentBattle.mysteryEncounter!;
console.log(encounter); setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
// Calculate boss mon fillRemaining: true,
const bossSpecies = getPokemonSpecies(Species.SNORLAX); });
const pokemonConfig: EnemyPokemonConfig = { encounter.startOfBattleEffects.push(
species: bossSpecies, {
isBoss: true, sourceBattlerIndex: BattlerIndex.ENEMY,
shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked targets: [BattlerIndex.PLAYER],
status: [ StatusEffect.SLEEP, 5 ], // Extra turns on timer for Snorlax's start of fight moves move: new PokemonMove(Moves.SNORE),
moveSet: [ Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT ], ignorePp: true,
modifierConfigs: [ },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType, sourceBattlerIndex: BattlerIndex.ENEMY,
stackCount: 2 targets: [BattlerIndex.PLAYER],
}, move: new PokemonMove(Moves.SNORE),
{ ignorePp: true,
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType, },
stackCount: 2 );
}, await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
], },
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), )
aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep .withSimpleOption(
}; {
const config: EnemyPartyConfig = { buttonLabel: `${namespace}:option.2.label`,
levelAdditiveModifier: 0.5, buttonTooltip: `${namespace}:option.2.tooltip`,
pokemonConfigs: [ pokemonConfig ], selected: [
}; {
encounter.enemyPartyConfigs = [ config ]; text: `${namespace}:option.2.selected`,
},
// Load animations/sfx for Snorlax fight start moves ],
loadCustomMovesForEncounter([ Moves.SNORE ]); },
async () => {
encounter.setDialogueToken("snorlaxName", getPokemonSpecies(Species.SNORLAX).getName()); // Fall asleep waiting for Snorlax
// Full heal party
return true; globalScene.unshiftPhase(new PartyHealPhase(true));
}) queueEncounterMessage(`${namespace}:option.2.rest_result`);
.setLocalizationKey(`${namespace}`) leaveEncounterWithoutBattle();
.withTitle(`${namespace}:title`) },
.withDescription(`${namespace}:description`) )
.withQuery(`${namespace}:query`) .withOption(
.withSimpleOption( MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
{ .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true))
buttonLabel: `${namespace}:option.1.label`, .withDialogue({
buttonTooltip: `${namespace}:option.1.tooltip`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.1.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}, })
async () => { .withOptionPhase(async () => {
// Pick battle // Steal the Snorlax's Leftovers
const encounter = globalScene.currentBattle.mysteryEncounter!; const instance = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: true }); setEncounterRewards({
encounter.startOfBattleEffects.push( guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS],
{ fillRemaining: false,
sourceBattlerIndex: BattlerIndex.ENEMY, });
targets: [ BattlerIndex.PLAYER ], // Snorlax exp to Pokemon that did the stealing
move: new PokemonMove(Moves.SNORE), setEncounterExp(instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp);
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(); leaveEncounterWithoutBattle();
} })
) .build(),
.withOption( )
MysteryEncounterOptionBuilder .build();
.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();

View File

@ -1,5 +1,12 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierTypeOption, initBattleWithEnemyConfig, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
generateModifierTypeOption,
initBattleWithEnemyConfig,
setEncounterExp,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -15,7 +22,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { getBiomeKey } from "#app/field/arena"; import { getBiomeKey } from "#app/field/arena";
import { Type } from "#enums/type"; import { PokemonType } from "#enums/pokemon-type";
import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type"; import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#app/data/trainer-config";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
@ -23,140 +30,153 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import {
getEncounterPokemonLevelForWave,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/teleportingHijinks"; const namespace = "mysteryEncounters/teleportingHijinks";
const MONEY_COST_MULTIPLIER = 1.75; const MONEY_COST_MULTIPLIER = 1.75;
const BIOME_CANDIDATES = [ Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND, Biome.WASTELAND, Biome.DOJO ]; const BIOME_CANDIDATES = [Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND, Biome.WASTELAND, Biome.DOJO];
const MACHINE_INTERFACING_TYPES = [ Type.ELECTRIC, Type.STEEL ]; const MACHINE_INTERFACING_TYPES = [PokemonType.ELECTRIC, PokemonType.STEEL];
/** /**
* Teleporting Hijinks encounter. * Teleporting Hijinks encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3817 | GitHub Issue #3817} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3817 | GitHub Issue #3817}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const TeleportingHijinksEncounter: MysteryEncounter = export const TeleportingHijinksEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TELEPORTING_HIJINKS) MysteryEncounterType.TELEPORTING_HIJINKS,
.withEncounterTier(MysteryEncounterTier.COMMON) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneRequirement(new WaveModulusRequirement([ 1, 2, 3 ], 10)) // Must be in first 3 waves after boss wave .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost .withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave
.withAutoHideIntroVisuals(false) .withSceneRequirement(new MoneyRequirement(0, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost
.withCatchAllowed(true) .withAutoHideIntroVisuals(false)
.withFleeAllowed(false) .withCatchAllowed(true)
.withIntroSpriteConfigs([ .withFleeAllowed(false)
{ .withIntroSpriteConfigs([
spriteKey: "teleporting_hijinks_teleporter", {
fileRoot: "mystery-encounters", spriteKey: "teleporting_hijinks_teleporter",
hasShadow: true, fileRoot: "mystery-encounters",
x: 4, hasShadow: true,
y: 4, x: 4,
yShadow: 1 y: 4,
} yShadow: 1,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
} text: `${namespace}:intro`,
]) },
.setLocalizationKey(`${namespace}`) ])
.withTitle(`${namespace}:title`) .setLocalizationKey(`${namespace}`)
.withDescription(`${namespace}:description`) .withTitle(`${namespace}:title`)
.withQuery(`${namespace}:query`) .withDescription(`${namespace}:description`)
.withOnInit(() => { .withQuery(`${namespace}:query`)
const encounter = globalScene.currentBattle.mysteryEncounter!; .withOnInit(() => {
const price = globalScene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER); const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.setDialogueToken("price", price.toString()); const price = globalScene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER);
encounter.misc = { encounter.setDialogueToken("price", price.toString());
price encounter.misc = {
}; price,
};
return true; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .withSceneMoneyRequirement(0, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost
.withSceneMoneyRequirement(0, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost .withDialogue({
.withDialogue({ buttonLabel: `${namespace}:option.1.label`,
buttonLabel: `${namespace}:option.1.label`, buttonTooltip: `${namespace}:option.1.tooltip`,
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`,
selected: [ selected: [
{ {
text: `${namespace}:option.3.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}, })
async () => { .withPreOptionPhase(async () => {
// Inspect the Machine // Update money
const encounter = globalScene.currentBattle.mysteryEncounter!; 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 // Init enemy
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true); const bossSpecies = globalScene.arena.randomSpecies(
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true); globalScene.currentBattle.waveIndex,
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); level,
const config: EnemyPartyConfig = { 0,
pokemonConfigs: [{ getPartyLuckValue(globalScene.getPlayerParty()),
true,
);
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = {
pokemonConfigs: [
{
level: level, level: level,
species: bossSpecies, species: bossSpecies,
dataSource: new PokemonData(bossPokemon), dataSource: new PokemonData(bossPokemon),
isBoss: true, isBoss: true,
}], },
}; ],
};
const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.STEEL ])!; const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.STEEL])!;
const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.ELECTRIC ])!; const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.ELECTRIC])!;
setEncounterRewards({ guaranteedModifierTypeOptions: [ magnet, metalCoat ], fillRemaining: true }); setEncounterRewards({
await transitionMysteryEncounterIntroVisuals(true, true); guaranteedModifierTypeOptions: [magnet, metalCoat],
await initBattleWithEnemyConfig(config); fillRemaining: true,
} });
) await transitionMysteryEncounterIntroVisuals(true, true);
.build(); await initBattleWithEnemyConfig(config);
},
)
.build();
async function doBiomeTransitionDialogueAndBattleInit() { async function doBiomeTransitionDialogueAndBattleInit() {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
@ -167,34 +187,43 @@ async function doBiomeTransitionDialogueAndBattleInit() {
// Show dialogue and transition biome // Show dialogue and transition biome
await showEncounterText(`${namespace}:transport`); await showEncounterText(`${namespace}:transport`);
await Promise.all([ animateBiomeChange(newBiome), transitionMysteryEncounterIntroVisuals() ]); await Promise.all([animateBiomeChange(newBiome), transitionMysteryEncounterIntroVisuals()]);
globalScene.updateBiomeWaveText(); globalScene.updateBiomeWaveText();
globalScene.playBgm(); globalScene.playBgm();
await showEncounterText(`${namespace}:attacked`); await showEncounterText(`${namespace}:attacked`);
// Init enemy // Init enemy
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true); const bossSpecies = globalScene.arena.randomSpecies(
globalScene.currentBattle.waveIndex,
level,
0,
getPartyLuckValue(globalScene.getPlayerParty()),
true,
);
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true); const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
// Defense/Spd buffs below wave 50, +1 to all stats otherwise // Defense/Spd buffs below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ? const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
[ Stat.DEF, Stat.SPDEF, Stat.SPD ] : globalScene.currentBattle.waveIndex < 50
[ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; ? [Stat.DEF, Stat.SPDEF, Stat.SPD]
: [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
pokemonConfigs: [{ pokemonConfigs: [
level: level, {
species: bossSpecies, level: level,
dataSource: new PokemonData(bossPokemon), species: bossSpecies,
isBoss: true, dataSource: new PokemonData(bossPokemon),
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], isBoss: true,
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
queueEncounterMessage(`${namespace}:boss_enraged`); mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); queueEncounterMessage(`${namespace}:boss_enraged`);
} globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
}], },
},
],
}; };
return config; return config;
@ -203,7 +232,7 @@ async function doBiomeTransitionDialogueAndBattleInit() {
async function animateBiomeChange(nextBiome: Biome) { async function animateBiomeChange(nextBiome: Biome) {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
globalScene.tweens.add({ globalScene.tweens.add({
targets: [ globalScene.arenaEnemy, globalScene.lastEnemyTrainer ], targets: [globalScene.arenaEnemy, globalScene.lastEnemyTrainer],
x: "+=300", x: "+=300",
duration: 2000, duration: 2000,
onComplete: () => { onComplete: () => {
@ -219,10 +248,10 @@ async function animateBiomeChange(nextBiome: Biome) {
globalScene.arenaPlayerTransition.setVisible(true); globalScene.arenaPlayerTransition.setVisible(true);
globalScene.tweens.add({ globalScene.tweens.add({
targets: [ globalScene.arenaPlayer, globalScene.arenaBgTransition, globalScene.arenaPlayerTransition ], targets: [globalScene.arenaPlayer, globalScene.arenaBgTransition, globalScene.arenaPlayerTransition],
duration: 1000, duration: 1000,
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
alpha: (target: any) => target === globalScene.arenaPlayer ? 0 : 1, alpha: (target: any) => (target === globalScene.arenaPlayer ? 0 : 1),
onComplete: () => { onComplete: () => {
globalScene.arenaBg.setTexture(bgTexture); globalScene.arenaBg.setTexture(bgTexture);
globalScene.arenaPlayer.setBiome(nextBiome); globalScene.arenaPlayer.setBiome(nextBiome);
@ -242,9 +271,9 @@ async function animateBiomeChange(nextBiome: Biome) {
targets: globalScene.arenaEnemy, targets: globalScene.arenaEnemy,
x: "-=300", x: "-=300",
}); });
} },
}); });
} },
}); });
}); });
} }

View File

@ -1,5 +1,9 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
handleMysteryEncounterBattleFailed,
initBattleWithEnemyConfig,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -24,7 +28,7 @@ import { EggTier } from "#enums/egg-type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { Type } from "#enums/type"; import { PokemonType } from "#enums/pokemon-type";
import { getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballTintColor } from "#app/data/pokeball";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
@ -39,7 +43,7 @@ const FINAL_STAGE_EVOLUTION_WAVE = 75;
const FRIENDSHIP_ADDED = 20; const FRIENDSHIP_ADDED = 20;
class BreederSpeciesEvolution { class BreederSpeciesEvolution {
species: Species; species: Species;
evolution: number; evolution: number;
@ -50,29 +54,65 @@ class BreederSpeciesEvolution {
} }
const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [ const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
[ Species.MUNCHLAX, new BreederSpeciesEvolution(Species.SNORLAX, SECOND_STAGE_EVOLUTION_WAVE) ], [Species.MUNCHLAX, new BreederSpeciesEvolution(Species.SNORLAX, SECOND_STAGE_EVOLUTION_WAVE)],
[ Species.HAPPINY, new BreederSpeciesEvolution(Species.CHANSEY, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.BLISSEY, FINAL_STAGE_EVOLUTION_WAVE) ], [
[ Species.MAGBY, new BreederSpeciesEvolution(Species.MAGMAR, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.MAGMORTAR, FINAL_STAGE_EVOLUTION_WAVE) ], Species.HAPPINY,
[ Species.ELEKID, new BreederSpeciesEvolution(Species.ELECTABUZZ, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ELECTIVIRE, FINAL_STAGE_EVOLUTION_WAVE) ], new BreederSpeciesEvolution(Species.CHANSEY, FIRST_STAGE_EVOLUTION_WAVE),
[ Species.RIOLU, new BreederSpeciesEvolution(Species.LUCARIO, SECOND_STAGE_EVOLUTION_WAVE) ], new BreederSpeciesEvolution(Species.BLISSEY, FINAL_STAGE_EVOLUTION_WAVE),
[ Species.BUDEW, new BreederSpeciesEvolution(Species.ROSELIA, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ROSERADE, FINAL_STAGE_EVOLUTION_WAVE) ], ],
[ Species.TOXEL, new BreederSpeciesEvolution(Species.TOXTRICITY, SECOND_STAGE_EVOLUTION_WAVE) ], [
[ Species.MIME_JR, new BreederSpeciesEvolution(Species.GALAR_MR_MIME, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.MR_RIME, FINAL_STAGE_EVOLUTION_WAVE) ] Species.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)[][] = [ const POOL_2_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ], [
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ], Species.PICHU,
[ Species.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE) ], new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE),
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE) ], new BreederSpeciesEvolution(Species.RAICHU, FINAL_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.PICHU,
[ Species.AZURILL, new BreederSpeciesEvolution(Species.MARILL, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.AZUMARILL, FINAL_STAGE_EVOLUTION_WAVE) ], new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE),
[ Species.WYNAUT, new BreederSpeciesEvolution(Species.WOBBUFFET, SECOND_STAGE_EVOLUTION_WAVE) ], new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE),
[ Species.CHINGLING, new BreederSpeciesEvolution(Species.CHIMECHO, SECOND_STAGE_EVOLUTION_WAVE) ], ],
[ Species.BONSLY, new BreederSpeciesEvolution(Species.SUDOWOODO, SECOND_STAGE_EVOLUTION_WAVE) ], [Species.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE)],
[ Species.MANTYKE, new BreederSpeciesEvolution(Species.MANTINE, 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 {@link https://github.com/pagefaultgames/pokerogue/issues/3818 | GitHub Issue #3818}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const TheExpertPokemonBreederEncounter: MysteryEncounter = export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER) MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER,
.withEncounterTier(MysteryEncounterTier.ULTRA) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withScenePartySizeRequirement(4, 6, true) // Must have at least 4 legal pokemon in party .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // These are set in onInit() .withScenePartySizeRequirement(4, 6, true) // Must have at least 4 legal pokemon in party
.withIntroDialogue([ .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, spriteKey: "expert_pokemon_breeder",
text: `${namespace}:intro_dialogue`, 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 // Determine the 3 pokemon the player can battle with
encounter.enemyPartyConfigs = [ let partyCopy = globalScene.getPlayerParty().slice(0);
getPartyConfig() 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; const pokemon1 = partyCopy[0];
encounter.spriteConfigs = [ const pokemon2 = partyCopy[1];
{ const pokemon3 = partyCopy[2];
spriteKey: cleffaSpecies.toString(), encounter.setDialogueToken("pokemon1Name", pokemon1.getNameToRender());
fileRoot: "pokemon", encounter.setDialogueToken("pokemon2Name", pokemon2.getNameToRender());
hasShadow: true, encounter.setDialogueToken("pokemon3Name", pokemon3.getNameToRender());
repeat: true,
x: 14,
y: -2,
yShadow: -2
},
{
spriteKey: "expert_pokemon_breeder",
fileRoot: "trainer",
hasShadow: true,
x: -14,
y: 4,
yShadow: 2
},
];
// Determine the 3 pokemon the player can battle with // Dialogue and egg calcs for Pokemon 1
let partyCopy = globalScene.getPlayerParty().slice(0); const [pokemon1CommonEggs, pokemon1RareEggs] = calculateEggRewardsForPokemon(pokemon1);
partyCopy = partyCopy let pokemon1Tooltip = getEncounterText(`${namespace}:option.1.tooltip_base`)!;
.filter(p => p.isAllowedInBattle()) if (pokemon1RareEggs > 0) {
.sort((a, b) => a.friendship - b.friendship); 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]; // Dialogue and egg calcs for Pokemon 2
const pokemon2 = partyCopy[1]; const [pokemon2CommonEggs, pokemon2RareEggs] = calculateEggRewardsForPokemon(pokemon2);
const pokemon3 = partyCopy[2]; let pokemon2Tooltip = getEncounterText(`${namespace}:option.2.tooltip_base`)!;
encounter.setDialogueToken("pokemon1Name", pokemon1.getNameToRender()); if (pokemon2RareEggs > 0) {
encounter.setDialogueToken("pokemon2Name", pokemon2.getNameToRender()); const eggsText = i18next.t(`${namespace}:numEggs`, {
encounter.setDialogueToken("pokemon3Name", pokemon3.getNameToRender()); 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 // Dialogue and egg calcs for Pokemon 3
const [ pokemon1CommonEggs, pokemon1RareEggs ] = calculateEggRewardsForPokemon(pokemon1); const [pokemon3CommonEggs, pokemon3RareEggs] = calculateEggRewardsForPokemon(pokemon3);
let pokemon1Tooltip = getEncounterText(`${namespace}:option.1.tooltip_base`)!; let pokemon3Tooltip = getEncounterText(`${namespace}:option.3.tooltip_base`)!;
if (pokemon1RareEggs > 0) { if (pokemon3RareEggs > 0) {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1RareEggs, rarity: i18next.t("egg:greatTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, {
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); count: pokemon3RareEggs,
encounter.setDialogueToken("pokemon1RareEggs", eggsText); rarity: i18next.t("egg:greatTier"),
} });
if (pokemon1CommonEggs > 0) { pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, {
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1CommonEggs, rarity: i18next.t("egg:defaultTier") }); eggs: eggsText,
pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); });
encounter.setDialogueToken("pokemon1CommonEggs", eggsText); encounter.setDialogueToken("pokemon3RareEggs", eggsText);
} }
encounter.options[0].dialogue!.buttonTooltip = pokemon1Tooltip; 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 encounter.misc = {
const [ pokemon2CommonEggs, pokemon2RareEggs ] = calculateEggRewardsForPokemon(pokemon2); pokemon1,
let pokemon2Tooltip = getEncounterText(`${namespace}:option.2.tooltip_base`)!; pokemon1CommonEggs,
if (pokemon2RareEggs > 0) { pokemon1RareEggs,
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2RareEggs, rarity: i18next.t("egg:greatTier") }); pokemon2,
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); pokemon2CommonEggs,
encounter.setDialogueToken("pokemon2RareEggs", eggsText); pokemon2RareEggs,
} pokemon3,
if (pokemon2CommonEggs > 0) { pokemon3CommonEggs,
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") }); pokemon3RareEggs,
pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); };
encounter.setDialogueToken("pokemon2CommonEggs", eggsText);
}
encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip;
// Dialogue and egg calcs for Pokemon 3 return true;
const [ pokemon3CommonEggs, pokemon3RareEggs ] = calculateEggRewardsForPokemon(pokemon3); })
let pokemon3Tooltip = getEncounterText(`${namespace}:option.3.tooltip_base`)!; .setLocalizationKey(`${namespace}`)
if (pokemon3RareEggs > 0) { .withTitle(`${namespace}:title`)
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3RareEggs, rarity: i18next.t("egg:greatTier") }); .withDescription(`${namespace}:description`)
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); .withQuery(`${namespace}:query`)
encounter.setDialogueToken("pokemon3RareEggs", eggsText); .withOption(
} MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
if (pokemon3CommonEggs > 0) { .withDialogue({
const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3CommonEggs, rarity: i18next.t("egg:defaultTier") }); buttonLabel: `${namespace}:option.1.label`,
pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); selected: [
encounter.setDialogueToken("pokemon3CommonEggs", eggsText); {
} speaker: trainerNameKey,
encounter.options[2].dialogue!.buttonTooltip = pokemon3Tooltip; text: `${namespace}:option.selected`,
},
],
})
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Spawn battle with first pokemon
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
encounter.misc = { const { pokemon1, pokemon1CommonEggs, pokemon1RareEggs } = encounter.misc;
pokemon1, encounter.misc.chosenPokemon = pokemon1;
pokemon1CommonEggs, encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
pokemon1RareEggs, const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs);
pokemon2, setEncounterRewards(
pokemon2CommonEggs, {
pokemon2RareEggs, guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
pokemon3, fillRemaining: true,
pokemon3CommonEggs, },
pokemon3RareEggs eggOptions,
}; () => doPostEncounterCleanup(),
);
return true; // Remove all Pokemon from the party except the chosen Pokemon
}) removePokemonFromPartyAndStoreHeldItems(encounter, pokemon1);
.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];
const { pokemon1, pokemon1CommonEggs, pokemon1RareEggs } = encounter.misc; // Configure outro dialogue for egg rewards
encounter.misc.chosenPokemon = pokemon1; encounter.dialogue.outro = [
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender()); {
const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs); speaker: trainerNameKey,
setEncounterRewards( text: `${namespace}:outro`,
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, },
eggOptions, ];
() => doPostEncounterCleanup()); 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 encounter.onGameOver = onGameOver;
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon1); 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 const { pokemon2, pokemon2CommonEggs, pokemon2RareEggs } = encounter.misc;
encounter.dialogue.outro = [ encounter.misc.chosenPokemon = pokemon2;
{ encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
speaker: trainerNameKey, const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs);
text: `${namespace}:outro`, setEncounterRewards(
}, {
]; guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
if (encounter.dialogueTokens.hasOwnProperty("pokemon1CommonEggs")) { fillRemaining: true,
encounter.dialogue.outro.push({ },
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1CommonEggs"] }), eggOptions,
}); () => doPostEncounterCleanup(),
} );
if (encounter.dialogueTokens.hasOwnProperty("pokemon1RareEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1RareEggs"] }),
});
}
encounter.onGameOver = onGameOver; // Remove all Pokemon from the party except the chosen Pokemon
await initBattleWithEnemyConfig(config); removePokemonFromPartyAndStoreHeldItems(encounter, pokemon2);
})
.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];
const { pokemon2, pokemon2CommonEggs, pokemon2RareEggs } = encounter.misc; // Configure outro dialogue for egg rewards
encounter.misc.chosenPokemon = pokemon2; encounter.dialogue.outro = [
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender()); {
const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs); speaker: trainerNameKey,
setEncounterRewards( text: `${namespace}:outro`,
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, },
eggOptions, ];
() => doPostEncounterCleanup()); 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 encounter.onGameOver = onGameOver;
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon2); 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 const { pokemon3, pokemon3CommonEggs, pokemon3RareEggs } = encounter.misc;
encounter.dialogue.outro = [ encounter.misc.chosenPokemon = pokemon3;
{ encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
speaker: trainerNameKey, const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs);
text: `${namespace}:outro`, setEncounterRewards(
}, {
]; guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL],
if (encounter.dialogueTokens.hasOwnProperty("pokemon2CommonEggs")) { fillRemaining: true,
encounter.dialogue.outro.push({ },
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2CommonEggs"] }), eggOptions,
}); () => doPostEncounterCleanup(),
} );
if (encounter.dialogueTokens.hasOwnProperty("pokemon2RareEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2RareEggs"] }),
});
}
encounter.onGameOver = onGameOver; // Remove all Pokemon from the party except the chosen Pokemon
await initBattleWithEnemyConfig(config); removePokemonFromPartyAndStoreHeldItems(encounter, pokemon3);
})
.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];
const { pokemon3, pokemon3CommonEggs, pokemon3RareEggs } = encounter.misc; // Configure outro dialogue for egg rewards
encounter.misc.chosenPokemon = pokemon3; encounter.dialogue.outro = [
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender()); {
const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs); speaker: trainerNameKey,
setEncounterRewards( text: `${namespace}:outro`,
{ guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, },
eggOptions, ];
() => doPostEncounterCleanup()); 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 encounter.onGameOver = onGameOver;
removePokemonFromPartyAndStoreHeldItems(encounter, pokemon3); await initBattleWithEnemyConfig(config);
})
// Configure outro dialogue for egg rewards .build(),
encounter.dialogue.outro = [ )
{ .withOutroDialogue([
speaker: trainerNameKey, {
text: `${namespace}:outro`, speaker: trainerNameKey,
}, text: `${namespace}:outro`,
]; },
if (encounter.dialogueTokens.hasOwnProperty("pokemon3CommonEggs")) { ])
encounter.dialogue.outro.push({ .build();
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();
function getPartyConfig(): EnemyPartyConfig { function getPartyConfig(): EnemyPartyConfig {
// Bug type superfan trainer config // Bug type superfan trainer config
@ -373,64 +466,79 @@ function getPartyConfig(): EnemyPartyConfig {
breederConfig.name = i18next.t(trainerNameKey); breederConfig.name = i18next.t(trainerNameKey);
// First mon is *always* this special cleffa // First mon is *always* this special cleffa
const cleffaSpecies = waveIndex < FIRST_STAGE_EVOLUTION_WAVE ? Species.CLEFFA : waveIndex < FINAL_STAGE_EVOLUTION_WAVE ? Species.CLEFAIRY : Species.CLEFABLE; const cleffaSpecies =
waveIndex < FIRST_STAGE_EVOLUTION_WAVE
? Species.CLEFFA
: waveIndex < FINAL_STAGE_EVOLUTION_WAVE
? Species.CLEFAIRY
: Species.CLEFABLE;
const baseConfig: EnemyPartyConfig = { const baseConfig: EnemyPartyConfig = {
trainerType: TrainerType.EXPERT_POKEMON_BREEDER, trainerType: TrainerType.EXPERT_POKEMON_BREEDER,
pokemonConfigs: [ pokemonConfigs: [
{ {
nickname: i18next.t(`${namespace}:cleffa_1_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }), nickname: i18next.t(`${namespace}:cleffa_1_nickname`, {
speciesName: getPokemonSpecies(cleffaSpecies).getName(),
}),
species: getPokemonSpecies(cleffaSpecies), species: getPokemonSpecies(cleffaSpecies),
isBoss: false, isBoss: false,
abilityIndex: 1, // Magic Guard abilityIndex: 1, // Magic Guard
shiny: false, shiny: false,
nature: Nature.ADAMANT, nature: Nature.ADAMANT,
moveSet: [ Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH ], moveSet: [Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH],
ivs: [ 31, 31, 31, 31, 31, 31 ], ivs: [31, 31, 31, 31, 31, 31],
tera: Type.STEEL, tera: PokemonType.STEEL,
} },
] ],
}; };
if (globalScene.arena.biomeType === Biome.SPACE) { if (globalScene.arena.biomeType === Biome.SPACE) {
// All 3 members always Cleffa line, but different configs // All 3 members always Cleffa line, but different configs
baseConfig.pokemonConfigs!.push({ baseConfig.pokemonConfigs!.push(
nickname: i18next.t(`${namespace}:cleffa_2_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }), {
species: getPokemonSpecies(cleffaSpecies), nickname: i18next.t(`${namespace}:cleffa_2_nickname`, {
isBoss: false, speciesName: getPokemonSpecies(cleffaSpecies).getName(),
abilityIndex: 1, // Magic Guard }),
shiny: true, species: getPokemonSpecies(cleffaSpecies),
variant: 1, isBoss: false,
nature: Nature.MODEST, abilityIndex: 1, // Magic Guard
moveSet: [ Moves.MOONBLAST, Moves.MYSTICAL_FIRE, Moves.ICE_BEAM, Moves.THUNDERBOLT ], shiny: true,
ivs: [ 31, 31, 31, 31, 31, 31 ] variant: 1,
}, nature: Nature.MODEST,
{ moveSet: [Moves.MOONBLAST, Moves.MYSTICAL_FIRE, Moves.ICE_BEAM, Moves.THUNDERBOLT],
nickname: i18next.t(`${namespace}:cleffa_3_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }), ivs: [31, 31, 31, 31, 31, 31],
species: getPokemonSpecies(cleffaSpecies), },
isBoss: false, {
abilityIndex: 2, // Friend Guard / Unaware nickname: i18next.t(`${namespace}:cleffa_3_nickname`, {
shiny: true, speciesName: getPokemonSpecies(cleffaSpecies).getName(),
variant: 2, }),
nature: Nature.BOLD, species: getPokemonSpecies(cleffaSpecies),
moveSet: [ Moves.TRI_ATTACK, Moves.STORED_POWER, Moves.TAKE_HEART, Moves.MOONLIGHT ], isBoss: false,
ivs: [ 31, 31, 31, 31, 31, 31 ] 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 { } else {
// Second member from pool 1 // Second member from pool 1
const pool1Species = getSpeciesFromPool(POOL_1_POKEMON, waveIndex); const pool1Species = getSpeciesFromPool(POOL_1_POKEMON, waveIndex);
// Third member from pool 2 // Third member from pool 2
const pool2Species = getSpeciesFromPool(POOL_2_POKEMON, waveIndex); const pool2Species = getSpeciesFromPool(POOL_2_POKEMON, waveIndex);
baseConfig.pokemonConfigs!.push({ baseConfig.pokemonConfigs!.push(
species: getPokemonSpecies(pool1Species), {
isBoss: false, species: getPokemonSpecies(pool1Species),
ivs: [ 31, 31, 31, 31, 31, 31 ] isBoss: false,
}, ivs: [31, 31, 31, 31, 31, 31],
{ },
species: getPokemonSpecies(pool2Species), {
isBoss: false, species: getPokemonSpecies(pool2Species),
ivs: [ 31, 31, 31, 31, 31, 31 ] isBoss: false,
}); ivs: [31, 31, 31, 31, 31, 31],
},
);
} }
return baseConfig; return baseConfig;
@ -471,7 +579,7 @@ function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number]
// 1 Common egg for every point leftover // 1 Common egg for every point leftover
numCommons += totalPoints % 4; numCommons += totalPoints % 4;
return [ numCommons, numRares ]; return [numCommons, numRares];
} }
function getEggOptions(commonEggs: number, rareEggs: number) { function getEggOptions(commonEggs: number, rareEggs: number) {
@ -484,7 +592,7 @@ function getEggOptions(commonEggs: number, rareEggs: number) {
pulled: false, pulled: false,
sourceType: EggSourceType.EVENT, sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription, eggDescriptor: eggDescription,
tier: EggTier.COMMON tier: EggTier.COMMON,
}); });
} }
} }
@ -494,7 +602,7 @@ function getEggOptions(commonEggs: number, rareEggs: number) {
pulled: false, pulled: false,
sourceType: EggSourceType.EVENT, sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription, eggDescriptor: eggDescription,
tier: EggTier.RARE tier: EggTier.RARE,
}); });
} }
} }
@ -508,11 +616,8 @@ function removePokemonFromPartyAndStoreHeldItems(encounter: MysteryEncounter, ch
party[chosenIndex] = party[0]; party[chosenIndex] = party[0];
party[0] = chosenPokemon; party[0] = chosenPokemon;
encounter.misc.originalParty = globalScene.getPlayerParty().slice(1); encounter.misc.originalParty = globalScene.getPlayerParty().slice(1);
encounter.misc.originalPartyHeldItems = encounter.misc.originalParty encounter.misc.originalPartyHeldItems = encounter.misc.originalParty.map(p => p.getHeldItems());
.map(p => p.getHeldItems()); globalScene["party"] = [chosenPokemon];
globalScene["party"] = [
chosenPokemon
];
} }
function restorePartyAndHeldItems() { function restorePartyAndHeldItems() {
@ -522,11 +627,11 @@ function restorePartyAndHeldItems() {
// Restore held items // Restore held items
const originalHeldItems = encounter.misc.originalPartyHeldItems; const originalHeldItems = encounter.misc.originalPartyHeldItems;
originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => { for (const pokemonHeldItemsList of originalHeldItems) {
pokemonHeldItemsList.forEach(heldItem => { for (const heldItem of pokemonHeldItemsList) {
globalScene.addModifier(heldItem, true, false, false, true); globalScene.addModifier(heldItem, true, false, false, true);
}); }
}); }
globalScene.updateModifiers(true); globalScene.updateModifiers(true);
} }
@ -542,7 +647,7 @@ function onGameOver() {
// Restore original party, player loses all friendship with chosen mon (it remains fainted) // Restore original party, player loses all friendship with chosen mon (it remains fainted)
restorePartyAndHeldItems(); restorePartyAndHeldItems();
const chosenPokemon = encounter.misc.chosenPokemon; const chosenPokemon = encounter.misc.chosenPokemon;
chosenPokemon.friendship = 0; chosenPokemon.friendship = 0;
// Clear all rewards that would have been earned // Clear all rewards that would have been earned
@ -571,7 +676,7 @@ function onGameOver() {
scale: 0.5, scale: 0.5,
onComplete: () => { onComplete: () => {
pokemon.leaveField(true, true, true); pokemon.leaveField(true, true, true);
} },
}); });
} }
@ -593,11 +698,10 @@ function onGameOver() {
y: "+=16", y: "+=16",
alpha: 1, alpha: 1,
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
duration: 750 duration: 750,
}); });
}); });
handleMysteryEncounterBattleFailed(true); handleMysteryEncounterBattleFailed(true);
return false; return false;

View File

@ -1,11 +1,19 @@
import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
leaveEncounterWithoutBattle,
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { catchPokemon, getRandomSpeciesByStarterCost, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import {
catchPokemon,
getRandomSpeciesByStarterCost,
getSpriteKeysFromPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
@ -35,143 +43,149 @@ const SHINY_MAGIKARP_WEIGHT = 100;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3799 | GitHub Issue #3799} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3799 | GitHub Issue #3799}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ThePokemonSalesmanEncounter: MysteryEncounter = export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN) MysteryEncounterType.THE_POKEMON_SALESMAN,
.withEncounterTier(MysteryEncounterTier.ULTRA) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneRequirement(new MoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withAutoHideIntroVisuals(false) .withSceneRequirement(new MoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
.withIntroSpriteConfigs([ .withAutoHideIntroVisuals(false)
{ .withIntroSpriteConfigs([
spriteKey: "pokemon_salesman", {
fileRoot: "mystery-encounters", spriteKey: "pokemon_salesman",
hasShadow: true fileRoot: "mystery-encounters",
} hasShadow: true,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
{ },
text: `${namespace}:intro_dialogue`, {
speaker: `${namespace}:speaker`, text: `${namespace}:intro_dialogue`,
}, speaker: `${namespace}:speaker`,
]) },
.setLocalizationKey(`${namespace}`) ])
.withTitle(`${namespace}:title`) .setLocalizationKey(`${namespace}`)
.withDescription(`${namespace}:description`) .withTitle(`${namespace}:title`)
.withQuery(`${namespace}:query`) .withDescription(`${namespace}:description`)
.withOnInit(() => { .withQuery(`${namespace}:query`)
const encounter = globalScene.currentBattle.mysteryEncounter!; .withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
let species = getSalesmanSpeciesOffer(); let species = getSalesmanSpeciesOffer();
let tries = 0; let tries = 0;
// Reroll any species that don't have HAs // Reroll any species that don't have HAs
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) { while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
species = getSalesmanSpeciesOffer(); species = getSalesmanSpeciesOffer();
tries++; tries++;
} }
let pokemon: PlayerPokemon; let pokemon: PlayerPokemon;
if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) { if (
// If no HA mon found or you roll 1%, give shiny Magikarp with random variant randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 ||
species = getPokemonSpecies(Species.MAGIKARP); isNullOrUndefined(species.abilityHidden) ||
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex, undefined, true); species.abilityHidden === Abilities.NONE
} else { ) {
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex); // If no HA mon found or you roll 1%, give shiny Magikarp with random variant
} species = getPokemonSpecies(Species.MAGIKARP);
pokemon.generateAndPopulateMoveset(); 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); const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(pokemon);
encounter.spriteConfigs.push({ encounter.spriteConfigs.push({
spriteKey: spriteKey, spriteKey: spriteKey,
fileRoot: fileRoot, fileRoot: fileRoot,
hasShadow: true, hasShadow: true,
repeat: true, repeat: true,
isPokemon: true, isPokemon: true,
isShiny: pokemon.shiny, isShiny: pokemon.shiny,
variant: pokemon.variant variant: pokemon.variant,
}); });
const starterTier = speciesStarterCosts[species.speciesId]; const starterTier = speciesStarterCosts[species.speciesId];
// Prices decrease by starter tier less than 5, but only reduces cost by half at max // 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); let priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER * (Math.max(starterTier, 2.5) / 5);
if (pokemon.shiny) { if (pokemon.shiny) {
// Always max price for shiny (flip HA back to normal), and add special messaging // Always max price for shiny (flip HA back to normal), and add special messaging
priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER; priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER;
pokemon.abilityIndex = 0; pokemon.abilityIndex = 0;
encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}:description_shiny`; encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}:description_shiny`;
encounter.options[0].dialogue!.buttonTooltip = `${namespace}:option.1.tooltip_shiny`; encounter.options[0].dialogue!.buttonTooltip = `${namespace}:option.1.tooltip_shiny`;
} }
const price = globalScene.getWaveMoneyAmount(priceMultiplier); const price = globalScene.getWaveMoneyAmount(priceMultiplier);
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender()); encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
encounter.setDialogueToken("price", price.toString()); encounter.setDialogueToken("price", price.toString());
encounter.misc = { encounter.misc = {
price: price, price: price,
pokemon: pokemon pokemon: pokemon,
}; };
pokemon.calculateStats(); pokemon.calculateStats();
return true; return true;
}) })
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .withHasDexProgress(true)
.withHasDexProgress(true) .withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
.withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2 .withDialogue({
.withDialogue({ buttonLabel: `${namespace}:option.1.label`,
buttonLabel: `${namespace}:option.1.label`, buttonTooltip: `${namespace}:option.1.tooltip`,
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`,
selected: [ selected: [
{ {
text: `${namespace}:option.2.selected`, text: `${namespace}:option.1.selected_message`,
}, },
], ],
}, })
async () => { .withOptionPhase(async () => {
// Leave encounter with no rewards or exp 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); 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. * @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
*/ */
export function getSalesmanSpeciesOffer(): PokemonSpecies { export function getSalesmanSpeciesOffer(): PokemonSpecies {
return getPokemonSpecies(getRandomSpeciesByStarterCost([ 0, 5 ], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false)); return getPokemonSpecies(
getRandomSpeciesByStarterCost([0, 5], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false),
);
} }

View File

@ -1,5 +1,12 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
initBattleWithEnemyConfig,
loadCustomMovesForEncounter,
leaveEncounterWithoutBattle,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
generateModifierType,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -35,179 +42,188 @@ const BST_INCREASE_VALUE = 10;
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3803 | GitHub Issue #3803} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3803 | GitHub Issue #3803}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const TheStrongStuffEncounter: MysteryEncounter = export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF) MysteryEncounterType.THE_STRONG_STUFF,
.withEncounterTier(MysteryEncounterTier.COMMON) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.COMMON)
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withMaxAllowedEncounters(1) .withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
.withHideWildIntroMessage(true) .withMaxAllowedEncounters(1)
.withAutoHideIntroVisuals(false) .withHideWildIntroMessage(true)
.withFleeAllowed(false) .withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([ .withFleeAllowed(false)
{ .withIntroSpriteConfigs([
spriteKey: "berry_juice", {
fileRoot: "items", spriteKey: "berry_juice",
hasShadow: true, fileRoot: "items",
isItem: true, hasShadow: true,
scale: 1.25, isItem: true,
x: -15, scale: 1.25,
y: 3, x: -15,
disableAnimation: true y: 3,
}, disableAnimation: true,
{ },
spriteKey: Species.SHUCKLE.toString(), {
fileRoot: "pokemon", spriteKey: Species.SHUCKLE.toString(),
hasShadow: true, fileRoot: "pokemon",
repeat: true, hasShadow: true,
scale: 1.25, repeat: true,
x: 20, scale: 1.25,
y: 10, x: 20,
yShadow: 7 y: 10,
}, yShadow: 7,
]) // Set in onInit() },
.withIntroDialogue([ ]) // Set in onInit()
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
]) },
.withOnInit(() => { ])
.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!; const encounter = globalScene.currentBattle.mysteryEncounter!;
// Do blackout and hide intro visuals during blackout
globalScene.time.delayedCall(750, () => {
transitionMysteryEncounterIntroVisuals(true, true, 50);
});
// Calculate boss mon // -15 to all base stats of highest BST (halved for HP), +10 to all base stats of rest of party (halved for HP)
const config: EnemyPartyConfig = { // Sort party by bst
levelAdditiveModifier: 1, const sortedParty = globalScene
disableSwitch: true, .getPlayerParty()
pokemonConfigs: [ .slice(0)
{ .sort((pokemon1, pokemon2) => {
species: getPokemonSpecies(Species.SHUCKLE), const pokemon1Bst = pokemon1.getSpeciesForm().getBaseStatTotal();
isBoss: true, const pokemon2Bst = pokemon2.getSpeciesForm().getBaseStatTotal();
bossSegments: 5, return pokemon2Bst - pokemon1Bst;
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 ]; 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("reductionValue", HIGH_BST_REDUCTION_VALUE.toString());
encounter.setDialogueToken("increaseValue", BST_INCREASE_VALUE.toString());
encounter.setDialogueToken("shuckleName", getPokemonSpecies(Species.SHUCKLE).getName()); await showEncounterText(`${namespace}:option.1.selected_2`, null, undefined, true);
encounter.dialogue.outro = [
{
text: `${namespace}:outro`,
},
];
setEncounterRewards({ fillRemaining: true });
leaveEncounterWithoutBattle(true);
return true; return true;
}) },
.setLocalizationKey(`${namespace}`) )
.withTitle(`${namespace}:title`) .withSimpleOption(
.withDescription(`${namespace}:description`) {
.withQuery(`${namespace}:query`) buttonLabel: `${namespace}:option.2.label`,
.withSimpleOption( buttonTooltip: `${namespace}:option.2.tooltip`,
{ selected: [
buttonLabel: `${namespace}:option.1.label`, {
buttonTooltip: `${namespace}:option.1.tooltip`, text: `${namespace}:option.2.selected`,
selected: [ },
{ ],
text: `${namespace}:option.1.selected` },
} async () => {
] // Pick battle
}, const encounter = globalScene.currentBattle.mysteryEncounter!;
async () => { setEncounterRewards({
const encounter = globalScene.currentBattle.mysteryEncounter!; guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW],
// Do blackout and hide intro visuals during blackout fillRemaining: true,
globalScene.time.delayedCall(750, () => { });
transitionMysteryEncounterIntroVisuals(true, true, 50); 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) encounter.dialogue.outro = [];
// Sort party by bst await transitionMysteryEncounterIntroVisuals(true, true, 500);
const sortedParty = globalScene.getPlayerParty().slice(0) await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
.sort((pokemon1, pokemon2) => { },
const pokemon1Bst = pokemon1.getSpeciesForm().getBaseStatTotal(); )
const pokemon2Bst = pokemon2.getSpeciesForm().getBaseStatTotal(); .build();
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();

View File

@ -1,5 +1,12 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
generateModifierType,
generateModifierTypeOption,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -13,7 +20,7 @@ import { Abilities } from "#enums/abilities";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { Type } from "#enums/type"; import { PokemonType } from "#enums/pokemon-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms";
@ -36,111 +43,115 @@ const namespace = "mysteryEncounters/theWinstrateChallenge";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3821 | GitHub Issue #3821} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3821 | GitHub Issue #3821}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const TheWinstrateChallengeEncounter: MysteryEncounter = export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_WINSTRATE_CHALLENGE) MysteryEncounterType.THE_WINSTRATE_CHALLENGE,
.withEncounterTier(MysteryEncounterTier.ROGUE) )
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withEncounterTier(MysteryEncounterTier.ROGUE)
.withIntroSpriteConfigs([ .withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
{ .withIntroSpriteConfigs([
spriteKey: "vito", {
fileRoot: "trainer", spriteKey: "vito",
hasShadow: false, fileRoot: "trainer",
x: 16, hasShadow: false,
y: -4 x: 16,
}, y: -4,
{ },
spriteKey: "vivi", {
fileRoot: "trainer", spriteKey: "vivi",
hasShadow: false, fileRoot: "trainer",
x: -14, hasShadow: false,
y: -4 x: -14,
}, y: -4,
{ },
spriteKey: "victor", {
fileRoot: "trainer", spriteKey: "victor",
hasShadow: true, fileRoot: "trainer",
x: -32 hasShadow: true,
}, x: -32,
{ },
spriteKey: "victoria", {
fileRoot: "trainer", spriteKey: "victoria",
hasShadow: true, fileRoot: "trainer",
x: 40, hasShadow: true,
}, x: 40,
{ },
spriteKey: "vicky", {
fileRoot: "trainer", spriteKey: "vicky",
hasShadow: true, fileRoot: "trainer",
x: 3, hasShadow: true,
y: 5, x: 3,
yShadow: 5 y: 5,
}, yShadow: 5,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
}, text: `${namespace}:intro`,
{ },
speaker: `${namespace}:speaker`, {
text: `${namespace}:intro_dialogue`, speaker: `${namespace}:speaker`,
}, text: `${namespace}:intro_dialogue`,
]) },
.withAutoHideIntroVisuals(false) ])
.withOnInit(() => { .withAutoHideIntroVisuals(false)
const encounter = globalScene.currentBattle.mysteryEncounter!; .withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Loaded back to front for pop() operations // Loaded back to front for pop() operations
encounter.enemyPartyConfigs.push(getVitoTrainerConfig()); encounter.enemyPartyConfigs.push(getVitoTrainerConfig());
encounter.enemyPartyConfigs.push(getVickyTrainerConfig()); encounter.enemyPartyConfigs.push(getVickyTrainerConfig());
encounter.enemyPartyConfigs.push(getViviTrainerConfig()); encounter.enemyPartyConfigs.push(getViviTrainerConfig());
encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig()); encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig());
encounter.enemyPartyConfigs.push(getVictorTrainerConfig()); encounter.enemyPartyConfigs.push(getVictorTrainerConfig());
return true; return true;
}) })
.setLocalizationKey(`${namespace}`) .setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}:option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}, },
async () => { async () => {
// Spawn 5 trainer battles back to back with Macho Brace in rewards // Spawn 5 trainer battles back to back with Macho Brace in rewards
globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = async () => { globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = async () => {
await endTrainerBattleAndShowDialogue(); await endTrainerBattleAndShowDialogue();
}; };
await transitionMysteryEncounterIntroVisuals(true, false); await transitionMysteryEncounterIntroVisuals(true, false);
await spawnNextTrainerOrEndEncounter(); await spawnNextTrainerOrEndEncounter();
} },
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}:option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
speaker: `${namespace}:speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}:option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, },
async () => { async () => {
// Refuse the challenge, they full heal the party and give the player a Rarer Candy // Refuse the challenge, they full heal the party and give the player a Rarer Candy
globalScene.unshiftPhase(new PartyHealPhase(true)); globalScene.unshiftPhase(new PartyHealPhase(true));
setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.RARER_CANDY ], fillRemaining: false }); setEncounterRewards({
leaveEncounterWithoutBattle(); guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY],
} fillRemaining: false,
) });
.build(); leaveEncounterWithoutBattle();
},
)
.build();
async function spawnNextTrainerOrEndEncounter() { async function spawnNextTrainerOrEndEncounter() {
const encounter = globalScene.currentBattle.mysteryEncounter!; 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 globalScene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in
const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!; const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!;
machoBrace.type.tier = ModifierTier.MASTER; machoBrace.type.tier = ModifierTier.MASTER;
setEncounterRewards({ guaranteedModifierTypeOptions: [ machoBrace ], fillRemaining: false }); setEncounterRewards({
guaranteedModifierTypeOptions: [machoBrace],
fillRemaining: false,
});
encounter.doContinueEncounter = undefined; encounter.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(false, MysteryEncounterMode.NO_BATTLE); leaveEncounterWithoutBattle(false, MysteryEncounterMode.NO_BATTLE);
} else { } else {
@ -168,6 +182,7 @@ async function spawnNextTrainerOrEndEncounter() {
} }
function endTrainerBattleAndShowDialogue(): Promise<void> { function endTrainerBattleAndShowDialogue(): Promise<void> {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: Consider refactoring to avoid async promise executor
return new Promise(async resolve => { return new Promise(async resolve => {
if (globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs.length === 0) { if (globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs.length === 0) {
// Battle is over // Battle is over
@ -182,7 +197,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
duration: 750, duration: 750,
onComplete: () => { onComplete: () => {
globalScene.field.remove(trainer, true); globalScene.field.remove(trainer, true);
} },
}); });
} }
@ -191,13 +206,19 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
} else { } else {
globalScene.arena.resetArenaEffects(); globalScene.arena.resetArenaEffects();
const playerField = globalScene.getPlayerField(); const playerField = globalScene.getPlayerField();
playerField.forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED)); for (const pokemon of playerField) {
pokemon.lapseTag(BattlerTagType.COMMANDED);
}
playerField.forEach((_, p) => globalScene.unshiftPhase(new ReturnPhase(p))); playerField.forEach((_, p) => globalScene.unshiftPhase(new ReturnPhase(p)));
for (const pokemon of globalScene.getPlayerParty()) { for (const pokemon of globalScene.getPlayerParty()) {
// Only trigger form change when Eiscue is in Noice form // Only trigger form change when Eiscue is in Noice form
// Hardcoded Eiscue for now in case it is fused with another pokemon // Hardcoded Eiscue for now in case it is fused with another pokemon
if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) { if (
pokemon.species.speciesId === Species.EISCUE &&
pokemon.hasAbility(Abilities.ICE_FACE) &&
pokemon.formIndex === 1
) {
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger);
} }
@ -222,7 +243,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
onComplete: () => { onComplete: () => {
globalScene.field.remove(trainer, true); globalScene.field.remove(trainer, true);
resolve(); resolve();
} },
}); });
} }
} }
@ -238,38 +259,38 @@ function getVictorTrainerConfig(): EnemyPartyConfig {
isBoss: false, isBoss: false,
abilityIndex: 0, // Guts abilityIndex: 0, // Guts
nature: Nature.ADAMANT, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false isTransferable: false,
}, },
{ {
modifier: generateModifierType(modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
isTransferable: false isTransferable: false,
}, },
] ],
}, },
{ {
species: getPokemonSpecies(Species.OBSTAGOON), species: getPokemonSpecies(Species.OBSTAGOON),
isBoss: false, isBoss: false,
abilityIndex: 1, // Guts abilityIndex: 1, // Guts
nature: Nature.ADAMANT, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false isTransferable: false,
}, },
{ {
modifier: generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
isTransferable: false isTransferable: false,
} },
] ],
} },
] ],
}; };
} }
@ -282,39 +303,43 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig {
isBoss: false, isBoss: false,
abilityIndex: 0, // Natural Cure abilityIndex: 0, // Natural Cure
nature: Nature.CALM, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType,
isTransferable: false isTransferable: false,
}, },
{ {
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
isTransferable: false isTransferable: false,
} },
] ],
}, },
{ {
species: getPokemonSpecies(Species.GARDEVOIR), species: getPokemonSpecies(Species.GARDEVOIR),
isBoss: false, isBoss: false,
formIndex: 1, formIndex: 1,
nature: Nature.TIMID, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.PSYCHIC ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
PokemonType.PSYCHIC,
]) as PokemonHeldItemModifierType,
stackCount: 1, stackCount: 1,
isTransferable: false isTransferable: false,
}, },
{ {
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FAIRY ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
PokemonType.FAIRY,
]) as PokemonHeldItemModifierType,
stackCount: 1, stackCount: 1,
isTransferable: false isTransferable: false,
} },
] ],
} },
] ],
}; };
} }
@ -327,53 +352,53 @@ function getViviTrainerConfig(): EnemyPartyConfig {
isBoss: false, isBoss: false,
abilityIndex: 3, // Lightning Rod abilityIndex: 3, // Lightning Rod
nature: Nature.ADAMANT, nature: Nature.ADAMANT,
moveSet: [ Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST ], moveSet: [Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST],
modifierConfigs: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
isTransferable: false isTransferable: false,
}, },
{ {
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [ Stat.HP ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
stackCount: 4, stackCount: 4,
isTransferable: false isTransferable: false,
} },
] ],
}, },
{ {
species: getPokemonSpecies(Species.BRELOOM), species: getPokemonSpecies(Species.BRELOOM),
isBoss: false, isBoss: false,
abilityIndex: 1, // Poison Heal abilityIndex: 1, // Poison Heal
nature: Nature.JOLLY, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [ Stat.HP ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
stackCount: 4, stackCount: 4,
isTransferable: false isTransferable: false,
}, },
{ {
modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
isTransferable: false isTransferable: false,
} },
] ],
}, },
{ {
species: getPokemonSpecies(Species.CAMERUPT), species: getPokemonSpecies(Species.CAMERUPT),
isBoss: false, isBoss: false,
formIndex: 1, formIndex: 1,
nature: Nature.CALM, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 3, stackCount: 3,
isTransferable: false isTransferable: false,
}, },
] ],
} },
] ],
}; };
} }
@ -386,15 +411,15 @@ function getVickyTrainerConfig(): EnemyPartyConfig {
isBoss: false, isBoss: false,
formIndex: 1, formIndex: 1,
nature: Nature.IMPISH, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType,
isTransferable: false isTransferable: false,
} },
] ],
} },
] ],
}; };
} }
@ -407,110 +432,110 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
isBoss: false, isBoss: false,
abilityIndex: 0, // Soundproof abilityIndex: 0, // Soundproof
nature: Nature.MODEST, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [ Stat.SPD ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
isTransferable: false isTransferable: false,
} },
] ],
}, },
{ {
species: getPokemonSpecies(Species.SWALOT), species: getPokemonSpecies(Species.SWALOT),
isBoss: false, isBoss: false,
abilityIndex: 2, // Gluttony abilityIndex: 2, // Gluttony
nature: Nature.QUIET, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
}, },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
}, },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
}, },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.STARF ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.STARF]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
}, },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SALAC ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SALAC]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
}, },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
}, },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LANSAT ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LANSAT]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
}, },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LIECHI ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LIECHI]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
}, },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.PETAYA ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.PETAYA]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
}, },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
}, },
{ {
modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LEPPA ]) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
} },
] ],
}, },
{ {
species: getPokemonSpecies(Species.DODRIO), species: getPokemonSpecies(Species.DODRIO),
isBoss: false, isBoss: false,
abilityIndex: 2, // Tangled Feet abilityIndex: 2, // Tangled Feet
nature: Nature.JOLLY, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
isTransferable: false isTransferable: false,
} },
] ],
}, },
{ {
species: getPokemonSpecies(Species.ALAKAZAM), species: getPokemonSpecies(Species.ALAKAZAM),
isBoss: false, isBoss: false,
formIndex: 1, formIndex: 1,
nature: Nature.BOLD, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
isTransferable: false isTransferable: false,
}, },
] ],
}, },
{ {
species: getPokemonSpecies(Species.DARMANITAN), species: getPokemonSpecies(Species.DARMANITAN),
isBoss: false, isBoss: false,
abilityIndex: 0, // Sheer Force abilityIndex: 0, // Sheer Force
nature: Nature.IMPISH, 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: [ modifierConfigs: [
{ {
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2, stackCount: 2,
isTransferable: false isTransferable: false,
}, },
] ],
} },
] ],
}; };
} }

View File

@ -1,7 +1,12 @@
import type { Ability } from "#app/data/ability"; import type { Ability } from "#app/data/ability";
import { allAbilities } from "#app/data/ability"; import { allAbilities } from "#app/data/ability";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import {
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getNatureName } from "#app/data/nature"; import { getNatureName } from "#app/data/nature";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
@ -35,360 +40,350 @@ const namespace = "mysteryEncounters/trainingSession";
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3802 | GitHub Issue #3802} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3802 | GitHub Issue #3802}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const TrainingSessionEncounter: MysteryEncounter = export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRAINING_SESSION) MysteryEncounterType.TRAINING_SESSION,
.withEncounterTier(MysteryEncounterTier.ULTRA) )
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withFleeAllowed(false) .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party
.withHideWildIntroMessage(true) .withFleeAllowed(false)
.withPreventGameStatsUpdates(true) // Do not count the Pokemon as seen or defeated since it is ours .withHideWildIntroMessage(true)
.withIntroSpriteConfigs([ .withPreventGameStatsUpdates(true) // Do not count the Pokemon as seen or defeated since it is ours
{ .withIntroSpriteConfigs([
spriteKey: "training_session_gear", {
fileRoot: "mystery-encounters", spriteKey: "training_session_gear",
hasShadow: true, fileRoot: "mystery-encounters",
y: 6, hasShadow: true,
x: 5, y: 6,
yShadow: -2 x: 5,
}, yShadow: -2,
]) },
.withIntroDialogue([ ])
{ .withIntroDialogue([
text: `${namespace}:intro`, {
} text: `${namespace}:intro`,
]) },
.setLocalizationKey(`${namespace}`) ])
.withTitle(`${namespace}:title`) .setLocalizationKey(`${namespace}`)
.withDescription(`${namespace}:description`) .withTitle(`${namespace}:title`)
.withQuery(`${namespace}:query`) .withDescription(`${namespace}:description`)
.withOption( .withQuery(`${namespace}:query`)
MysteryEncounterOptionBuilder .withOption(
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}) })
.withPreOptionPhase(async (): Promise<boolean> => { .withPreOptionPhase(async (): Promise<boolean> => {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => { const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.misc = { encounter.misc = {
playerPokemon: pokemon, 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 // Only Pokemon that are not KOed/legal can be trained
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`); return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
}; };
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon; const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
// Spawn light training session with chosen pokemon // Spawn medium training session with chosen pokemon
// Every 50 waves, add +1 boss segment, capping at 5 // Every 40 waves, add +1 boss segment, capping at 6
const segments = Math.min( const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 40), 6);
2 + Math.floor(globalScene.currentBattle.waveIndex / 50), const modifiers = new ModifiersHolder();
5 const config = getEnemyConfig(playerPokemon, segments, modifiers);
); globalScene.removePokemonFromPlayerParty(playerPokemon, false);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(playerPokemon, segments, modifiers);
globalScene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => { const onBeforeRewardsPhase = () => {
encounter.setDialogueToken("stat1", "-"); queueEncounterMessage(`${namespace}:option.2.finished`);
encounter.setDialogueToken("stat2", "-"); // Add the pokemon back to party with Nature change
// Add the pokemon back to party with IV boost playerPokemon.setCustomNature(encounter.misc.chosenNature);
let ivIndexes: any[] = []; globalScene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature);
playerPokemon.ivs.forEach((iv, index) => {
if (iv < 31) {
ivIndexes.push({ iv: iv, index: index });
}
});
// Improves 2 random non-maxed IVs // Add pokemon and modifiers back
// +10 if IV is < 10, +5 if between 10-20, and +3 if > 20 globalScene.getPlayerParty().push(playerPokemon);
// A 0-4 starting IV will cap in 6 encounters (assuming you always rolled that IV) for (const mod of modifiers.value) {
// 5-14 starting IV caps in 5 encounters mod.pokemonId = playerPokemon.id;
// 15-19 starting IV caps in 4 encounters globalScene.addModifier(mod, true, false, false, true);
// 20-24 starting IV caps in 3 encounters }
// 25-27 starting IV caps in 2 encounters globalScene.updateModifiers(true);
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 setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
if (ivToChange.iv <= 21 && ivToChange.iv - (1 % 5) === 0) {
newVal += 1;
}
newVal += ivToChange.iv <= 10 ? 10 : ivToChange.iv <= 20 ? 5 : 3; await initBattleWithEnemyConfig(config);
newVal = Math.min(newVal, 31); })
playerPokemon.ivs[ivToChange.index] = newVal; .build(),
improvedCount++; )
} .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) { const optionSelectItems: OptionSelectItem[] = [];
playerPokemon.calculateStats(); abilities.forEach((ability: Ability, index) => {
globalScene.gameData.updateSpeciesDexIvs(playerPokemon.species.getRootSpeciesId(true), playerPokemon.ivs); if (!optionSelectItems.some(o => o.label === ability.name)) {
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 = { const option: OptionSelectItem = {
label: getNatureName(nature, true, true, true, globalScene.uiTheme), label: ability.name,
handler: () => { handler: () => {
// Pokemon and second option selected // Pokemon and ability selected
encounter.setDialogueToken("nature", getNatureName(nature)); encounter.setDialogueToken("ability", ability.name);
encounter.misc = { encounter.misc = {
playerPokemon: pokemon, playerPokemon: pokemon,
chosenNature: nature, abilityIndex: index,
}; };
return true; return true;
}, },
onHover: () => {
showEncounterText(ability.description, 0, 0, false);
},
}; };
return option; optionSelectItems.push(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);
} }
globalScene.updateModifiers(true); });
};
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase); return optionSelectItems;
};
await initBattleWithEnemyConfig(config); // Only Pokemon that are not KOed/legal can be trained
}) const selectableFilter = (pokemon: Pokemon) => {
.build() return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`);
) };
.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)]);
const optionSelectItems: OptionSelectItem[] = []; return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
abilities.forEach((ability: Ability, index) => { })
if (!optionSelectItems.some(o => o.label === ability.name)) { .withOptionPhase(async () => {
const option: OptionSelectItem = { const encounter = globalScene.currentBattle.mysteryEncounter!;
label: ability.name, const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
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 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 onBeforeRewardsPhase = () => {
const selectableFilter = (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.3.finished`);
return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`); // Add the pokemon back to party with ability change
}; const abilityIndex = encounter.misc.abilityIndex;
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); if (playerPokemon.getFusionSpeciesForm()) {
}) playerPokemon.fusionAbilityIndex = abilityIndex;
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
// Spawn hard training session with chosen pokemon // Only update the fusion's dex data if the Pokemon is already caught in dex (ignore rentals)
// Every 30 waves, add +1 boss segment, capping at 6 const rootFusionSpecies = playerPokemon.fusionSpecies?.getRootSpeciesId();
// Also starts with +1 to all stats if (
const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 30), 6); !isNullOrUndefined(rootFusionSpecies) &&
const modifiers = new ModifiersHolder(); speciesStarterCosts.hasOwnProperty(rootFusionSpecies) &&
const config = getEnemyConfig(playerPokemon, segments, modifiers); !!globalScene.gameData.dexData[rootFusionSpecies].caughtAttr
config.pokemonConfigs![0].tags = [ ) {
BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON, globalScene.gameData.starterData[rootFusionSpecies].abilityAttr |=
]; playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2
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
? 1 << playerPokemon.fusionAbilityIndex ? 1 << playerPokemon.fusionAbilityIndex
: AbilityAttr.ABILITY_HIDDEN; : AbilityAttr.ABILITY_HIDDEN;
}
} else {
playerPokemon.abilityIndex = abilityIndex;
} }
} else {
playerPokemon.abilityIndex = abilityIndex;
}
playerPokemon.calculateStats(); playerPokemon.calculateStats();
globalScene.gameData.setPokemonCaught(playerPokemon, false); globalScene.gameData.setPokemonCaught(playerPokemon, false);
// Add pokemon and mods back // Add pokemon and mods back
globalScene.getPlayerParty().push(playerPokemon); globalScene.getPlayerParty().push(playerPokemon);
for (const mod of modifiers.value) { for (const mod of modifiers.value) {
mod.pokemonId = playerPokemon.id; mod.pokemonId = playerPokemon.id;
globalScene.addModifier(mod, true, false, false, true); globalScene.addModifier(mod, true, false, false, true);
} }
globalScene.updateModifiers(true); globalScene.updateModifiers(true);
}; };
setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase); setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase);
await initBattleWithEnemyConfig(config); await initBattleWithEnemyConfig(config);
}) })
.build() .build(),
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}:option.4.label`, buttonLabel: `${namespace}:option.4.label`,
buttonTooltip: `${namespace}:option.4.tooltip`, buttonTooltip: `${namespace}:option.4.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}:option.4.selected`, text: `${namespace}:option.4.selected`,
}, },
], ],
}, },
async () => { async () => {
// Leave encounter with no rewards or exp // Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
return true; return true;
} },
) )
.build(); .build();
function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig { function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig {
playerPokemon.resetSummonData(); playerPokemon.resetSummonData();
// Passes modifiers by reference // Passes modifiers by reference
modifiers.value = playerPokemon.getHeldItems(); modifiers.value = playerPokemon.getHeldItems();
const modifierConfigs = modifiers.value.map((mod) => { const modifierConfigs = modifiers.value.map(mod => {
return { return {
modifier: mod.clone(), modifier: mod.clone(),
isTransferable: false, isTransferable: false,
stackCount: mod.stackCount stackCount: mod.stackCount,
}; };
}) as HeldModifierConfig[]; }) as HeldModifierConfig[];
@ -410,6 +405,4 @@ function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifier
class ModifiersHolder { class ModifiersHolder {
public value: PokemonHeldItemModifier[] = []; public value: PokemonHeldItemModifier[] = [];
constructor() {}
} }

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