diff --git a/eslint.config.js b/eslint.config.js index c371f073f76..2f2b466c66f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,5 +1,5 @@ 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 importX from 'eslint-plugin-import-x'; @@ -16,15 +16,15 @@ export default [ '@typescript-eslint': tseslint }, rules: { - "eqeqeq": ["error", "always"], // Enforces the use of === and !== instead of == and != - "indent": ["error", 2], // Enforces a 2-space indentation + "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", // Prefers the use of const for variables that are never reassigned + "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 rest. + "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 @@ -32,14 +32,14 @@ export default [ "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"], + "@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 comma + "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 diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 00000000000..f4dfa7d4cb2 --- /dev/null +++ b/global.d.ts @@ -0,0 +1,14 @@ +import type { SetupServerApi } from "msw/node"; + +export {}; + +declare global { + /** + * Only used in testing. + * Can technically be undefined/null but for ease of use we are going to assume it is always defined. + * Used to load i18n files exclusively. + * + * To set up your own server in a test see `game_data.test.ts` + */ + var i18nServer: SetupServerApi; +} diff --git a/package-lock.json b/package-lock.json index ee2708b38f5..be946306471 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pokemon-rogue-battle", - "version": "1.0.4", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pokemon-rogue-battle", - "version": "1.0.4", + "version": "1.1.0", "hasInstallScript": true, "dependencies": { "@material/material-color-utilities": "^0.2.7", diff --git a/package.json b/package.json index d8d7f5e8db8..a31296d1644 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.0.4", + "version": "1.1.0", "type": "module", "scripts": { "start": "vite", @@ -20,7 +20,7 @@ "depcruise": "depcruise src", "depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg", "create-test": "node ./create-test-boilerplate.js", - "postinstall": "npx lefthook install && npx lefthook run post-merge" + "postinstall": "npx lefthook install && npx lefthook run post-merge" }, "devDependencies": { "@eslint/js": "^9.3.0", diff --git a/public/audio/cry/718-10-complete.m4a b/public/audio/cry/718-10-complete.m4a new file mode 100644 index 00000000000..94d95360553 Binary files /dev/null and b/public/audio/cry/718-10-complete.m4a differ diff --git a/public/fonts/pokemon-emerald-pro.ttf b/public/fonts/pokemon-emerald-pro.ttf index 509bd1aae86..84e49ebbc40 100644 Binary files a/public/fonts/pokemon-emerald-pro.ttf and b/public/fonts/pokemon-emerald-pro.ttf differ diff --git a/public/images/items.json b/public/images/items.json index 779823d1293..05d021b6a06 100644 --- a/public/images/items.json +++ b/public/images/items.json @@ -3416,12 +3416,12 @@ "rotated": false, "trimmed": true, "sourceSize": { - "w": 24, - "h": 24 + "w": 32, + "h": 32 }, "spriteSourceSize": { - "x": 1, - "y": 2, + "x": 5, + "y": 7, "w": 22, "h": 19 }, @@ -8415,6 +8415,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:934ea4080bad980d4fea720cc771f133:ed564bc47b79b15a763de57045178e88:110e074689c9edd2c54833ce2e4d9270$" + "smartupdate": "$TexturePacker:SmartUpdate:9ef21166268f7487fc9ff8d0f9b996e4:82658ac7bdd4c2b417e1f59168179262:110e074689c9edd2c54833ce2e4d9270$" } } diff --git a/public/images/items.png b/public/images/items.png index 5f032b30cfb..8aaa0281c00 100644 Binary files a/public/images/items.png and b/public/images/items.png differ diff --git a/public/images/items/black_sludge.png b/public/images/items/black_sludge.png index 39684a40310..37aa31de43e 100644 Binary files a/public/images/items/black_sludge.png and b/public/images/items/black_sludge.png differ diff --git a/public/images/pokemon/275.png b/public/images/pokemon/275.png index 07a6fe725ad..61f98ed1655 100644 Binary files a/public/images/pokemon/275.png and b/public/images/pokemon/275.png differ diff --git a/public/images/pokemon/281.json b/public/images/pokemon/281.json index 8e865cdc935..cb1a43f256f 100644 --- a/public/images/pokemon/281.json +++ b/public/images/pokemon/281.json @@ -399,13 +399,13 @@ "x": 0, "y": 6, "w": 36, - "h": 55 + "h": 54 }, "frame": { "x": 72, "y": 55, "w": 36, - "h": 55 + "h": 54 } }, { @@ -420,13 +420,13 @@ "x": 0, "y": 6, "w": 36, - "h": 55 + "h": 54 }, "frame": { "x": 72, "y": 55, "w": 36, - "h": 55 + "h": 54 } }, { diff --git a/public/images/pokemon/451.json b/public/images/pokemon/451.json index 3a320a87c61..0e99c96f876 100644 --- a/public/images/pokemon/451.json +++ b/public/images/pokemon/451.json @@ -1,715 +1,2330 @@ -{ "frames": [ - { - "filename": "0001.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0002.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0003.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0004.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0005.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0006.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0007.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0008.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0009.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0010.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0011.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0012.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0013.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0014.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0015.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0016.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0017.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0018.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0019.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0020.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0021.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0022.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0023.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0024.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0025.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0026.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0027.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0028.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0029.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0030.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0031.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0032.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0033.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0034.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0035.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0036.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0037.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0038.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0039.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0040.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0041.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0042.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0043.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0044.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0045.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0046.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0047.png", - "frame": { "x": 232, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0048.png", - "frame": { "x": 232, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0049.png", - "frame": { "x": 117, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0050.png", - "frame": { "x": 117, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0051.png", - "frame": { "x": 117, "y": 42, "w": 60, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 60, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0052.png", - "frame": { "x": 117, "y": 42, "w": 60, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 60, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0053.png", - "frame": { "x": 132, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0054.png", - "frame": { "x": 132, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0055.png", - "frame": { "x": 0, "y": 0, "w": 68, "h": 40 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 6, "w": 68, "h": 40 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0056.png", - "frame": { "x": 0, "y": 0, "w": 68, "h": 40 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 6, "w": 68, "h": 40 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0057.png", - "frame": { "x": 195, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0058.png", - "frame": { "x": 195, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0059.png", - "frame": { "x": 258, "y": 0, "w": 61, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 3, "w": 61, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0060.png", - "frame": { "x": 258, "y": 0, "w": 61, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 3, "w": 61, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0061.png", - "frame": { "x": 58, "y": 42, "w": 59, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 2, "w": 59, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0062.png", - "frame": { "x": 58, "y": 42, "w": 59, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 2, "w": 59, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0063.png", - "frame": { "x": 0, "y": 40, "w": 58, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 58, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0064.png", - "frame": { "x": 0, "y": 40, "w": 58, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 58, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0065.png", - "frame": { "x": 177, "y": 84, "w": 55, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 55, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0066.png", - "frame": { "x": 177, "y": 84, "w": 55, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 55, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0067.png", - "frame": { "x": 0, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0068.png", - "frame": { "x": 0, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0069.png", - "frame": { "x": 112, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0070.png", - "frame": { "x": 112, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0071.png", - "frame": { "x": 167, "y": 130, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0072.png", - "frame": { "x": 167, "y": 130, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0073.png", - "frame": { "x": 0, "y": 173, "w": 54, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 54, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0074.png", - "frame": { "x": 0, "y": 173, "w": 54, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 54, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0075.png", - "frame": { "x": 54, "y": 177, "w": 52, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 52, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0076.png", - "frame": { "x": 54, "y": 177, "w": 52, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 52, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0077.png", - "frame": { "x": 210, "y": 176, "w": 53, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 2, "w": 53, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0078.png", - "frame": { "x": 210, "y": 176, "w": 53, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 2, "w": 53, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0079.png", - "frame": { "x": 158, "y": 174, "w": 52, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 52, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0080.png", - "frame": { "x": 158, "y": 174, "w": 52, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 52, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0081.png", - "frame": { "x": 222, "y": 131, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0082.png", - "frame": { "x": 222, "y": 131, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0083.png", - "frame": { "x": 275, "y": 132, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0084.png", - "frame": { "x": 275, "y": 132, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0085.png", - "frame": { "x": 55, "y": 131, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0086.png", - "frame": { "x": 55, "y": 131, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0087.png", - "frame": { "x": 107, "y": 173, "w": 51, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 51, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0088.png", - "frame": { "x": 107, "y": 173, "w": 51, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 51, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - } - ], - "meta": { - "app": "https://www.aseprite.org/", - "version": "1.3.8.1-x64", - "image": "451.png", - "format": "I8", - "size": { "w": 339, "h": 221 }, - "scale": "1" - } +{ + "textures": [ + { + "image": "451.png", + "format": "RGBA8888", + "size": { + "w": 281, + "h": 281 + }, + "scale": 1, + "frames": [ + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 179, + "y": 87, + "w": 57, + "h": 45 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 179, + "y": 87, + "w": 57, + "h": 45 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 0, + "y": 129, + "w": 57, + "h": 45 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 0, + "y": 129, + "w": 57, + "h": 45 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 57, + "y": 132, + "w": 57, + "h": 45 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 57, + "y": 132, + "w": 57, + "h": 45 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 56, + "h": 45 + }, + "frame": { + "x": 114, + "y": 132, + "w": 56, + "h": 45 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 56, + "h": 45 + }, + "frame": { + "x": 114, + "y": 132, + "w": 56, + "h": 45 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 170, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 170, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 224, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 224, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 53, + "h": 46 + }, + "frame": { + "x": 228, + "y": 177, + "w": 53, + "h": 46 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 53, + "h": 46 + }, + "frame": { + "x": 228, + "y": 177, + "w": 53, + "h": 46 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 220, + "w": 54, + "h": 46 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 220, + "w": 54, + "h": 46 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 111, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 111, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 166, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 166, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 221, + "y": 223, + "w": 54, + "h": 46 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 221, + "y": 223, + "w": 54, + "h": 46 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:e303c68c1876b77078f3e1fd4372a4ce:84139d6b94cea0f3c45dbd8fa7109c3d:c79e17c206de27e3b7f1ce96f7df8e51$" + } } diff --git a/public/images/pokemon/451.png b/public/images/pokemon/451.png index 716e8a08041..fac8f5a0170 100644 Binary files a/public/images/pokemon/451.png and b/public/images/pokemon/451.png differ diff --git a/public/images/pokemon/6706.png b/public/images/pokemon/6706.png index 44f627acd10..e967b550871 100644 Binary files a/public/images/pokemon/6706.png and b/public/images/pokemon/6706.png differ diff --git a/public/images/pokemon/back/6706.png b/public/images/pokemon/back/6706.png index e7620078a5b..82c64e98535 100644 Binary files a/public/images/pokemon/back/6706.png and b/public/images/pokemon/back/6706.png differ diff --git a/public/images/pokemon/exp/6706.png b/public/images/pokemon/exp/6706.png index ea745dfb04b..77b10183ec7 100644 Binary files a/public/images/pokemon/exp/6706.png and b/public/images/pokemon/exp/6706.png differ diff --git a/public/images/pokemon/exp/705.png b/public/images/pokemon/exp/705.png index 3413bcd9fa3..670e8be5d51 100644 Binary files a/public/images/pokemon/exp/705.png and b/public/images/pokemon/exp/705.png differ diff --git a/public/images/pokemon/exp/back/6706.png b/public/images/pokemon/exp/back/6706.png index 4bda5b08d10..7842bbbffad 100644 Binary files a/public/images/pokemon/exp/back/6706.png and b/public/images/pokemon/exp/back/6706.png differ diff --git a/public/images/pokemon/female/275.png b/public/images/pokemon/female/275.png index 7f251793a15..c3c358716b2 100644 Binary files a/public/images/pokemon/female/275.png and b/public/images/pokemon/female/275.png differ diff --git a/public/images/pokemon/shiny/275.png b/public/images/pokemon/shiny/275.png index fee57d64804..de8271cdbd2 100644 Binary files a/public/images/pokemon/shiny/275.png and b/public/images/pokemon/shiny/275.png differ diff --git a/public/images/pokemon/shiny/281.json b/public/images/pokemon/shiny/281.json index 684be77edf9..64e10c7f9a6 100644 --- a/public/images/pokemon/shiny/281.json +++ b/public/images/pokemon/shiny/281.json @@ -399,13 +399,13 @@ "x": 0, "y": 6, "w": 36, - "h": 55 + "h": 54 }, "frame": { "x": 72, "y": 55, "w": 36, - "h": 55 + "h": 54 } }, { @@ -420,13 +420,13 @@ "x": 0, "y": 6, "w": 36, - "h": 55 + "h": 54 }, "frame": { "x": 72, "y": 55, "w": 36, - "h": 55 + "h": 54 } }, { diff --git a/public/images/pokemon/shiny/451.json b/public/images/pokemon/shiny/451.json index 3a320a87c61..f492cdbcb04 100644 --- a/public/images/pokemon/shiny/451.json +++ b/public/images/pokemon/shiny/451.json @@ -1,715 +1,2330 @@ -{ "frames": [ - { - "filename": "0001.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0002.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0003.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0004.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0005.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0006.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0007.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0008.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0009.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0010.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0011.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0012.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0013.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0014.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0015.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0016.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0017.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0018.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0019.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0020.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0021.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0022.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0023.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0024.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0025.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0026.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0027.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0028.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0029.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0030.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0031.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0032.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0033.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0034.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0035.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0036.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0037.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0038.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0039.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0040.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0041.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0042.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0043.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0044.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0045.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0046.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0047.png", - "frame": { "x": 232, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0048.png", - "frame": { "x": 232, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0049.png", - "frame": { "x": 117, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0050.png", - "frame": { "x": 117, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0051.png", - "frame": { "x": 117, "y": 42, "w": 60, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 60, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0052.png", - "frame": { "x": 117, "y": 42, "w": 60, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 60, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0053.png", - "frame": { "x": 132, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0054.png", - "frame": { "x": 132, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0055.png", - "frame": { "x": 0, "y": 0, "w": 68, "h": 40 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 6, "w": 68, "h": 40 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0056.png", - "frame": { "x": 0, "y": 0, "w": 68, "h": 40 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 6, "w": 68, "h": 40 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0057.png", - "frame": { "x": 195, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0058.png", - "frame": { "x": 195, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0059.png", - "frame": { "x": 258, "y": 0, "w": 61, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 3, "w": 61, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0060.png", - "frame": { "x": 258, "y": 0, "w": 61, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 3, "w": 61, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0061.png", - "frame": { "x": 58, "y": 42, "w": 59, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 2, "w": 59, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0062.png", - "frame": { "x": 58, "y": 42, "w": 59, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 2, "w": 59, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0063.png", - "frame": { "x": 0, "y": 40, "w": 58, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 58, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0064.png", - "frame": { "x": 0, "y": 40, "w": 58, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 58, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0065.png", - "frame": { "x": 177, "y": 84, "w": 55, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 55, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0066.png", - "frame": { "x": 177, "y": 84, "w": 55, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 55, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0067.png", - "frame": { "x": 0, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0068.png", - "frame": { "x": 0, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0069.png", - "frame": { "x": 112, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0070.png", - "frame": { "x": 112, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0071.png", - "frame": { "x": 167, "y": 130, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0072.png", - "frame": { "x": 167, "y": 130, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0073.png", - "frame": { "x": 0, "y": 173, "w": 54, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 54, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0074.png", - "frame": { "x": 0, "y": 173, "w": 54, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 54, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0075.png", - "frame": { "x": 54, "y": 177, "w": 52, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 52, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0076.png", - "frame": { "x": 54, "y": 177, "w": 52, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 52, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0077.png", - "frame": { "x": 210, "y": 176, "w": 53, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 2, "w": 53, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0078.png", - "frame": { "x": 210, "y": 176, "w": 53, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 2, "w": 53, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0079.png", - "frame": { "x": 158, "y": 174, "w": 52, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 52, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0080.png", - "frame": { "x": 158, "y": 174, "w": 52, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 52, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0081.png", - "frame": { "x": 222, "y": 131, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0082.png", - "frame": { "x": 222, "y": 131, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0083.png", - "frame": { "x": 275, "y": 132, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0084.png", - "frame": { "x": 275, "y": 132, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0085.png", - "frame": { "x": 55, "y": 131, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0086.png", - "frame": { "x": 55, "y": 131, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0087.png", - "frame": { "x": 107, "y": 173, "w": 51, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 51, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0088.png", - "frame": { "x": 107, "y": 173, "w": 51, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 51, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - } - ], - "meta": { - "app": "https://www.aseprite.org/", - "version": "1.3.8.1-x64", - "image": "451.png", - "format": "I8", - "size": { "w": 339, "h": 221 }, - "scale": "1" - } +{ + "textures": [ + { + "image": "451.png", + "format": "RGBA8888", + "size": { + "w": 281, + "h": 281 + }, + "scale": 1, + "frames": [ + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 179, + "y": 87, + "w": 57, + "h": 45 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 179, + "y": 87, + "w": 57, + "h": 45 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 0, + "y": 129, + "w": 57, + "h": 45 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 0, + "y": 129, + "w": 57, + "h": 45 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 57, + "y": 132, + "w": 57, + "h": 45 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 57, + "y": 132, + "w": 57, + "h": 45 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 56, + "h": 45 + }, + "frame": { + "x": 114, + "y": 132, + "w": 56, + "h": 45 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 56, + "h": 45 + }, + "frame": { + "x": 114, + "y": 132, + "w": 56, + "h": 45 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 170, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 170, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 224, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 224, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 53, + "h": 46 + }, + "frame": { + "x": 228, + "y": 177, + "w": 53, + "h": 46 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 53, + "h": 46 + }, + "frame": { + "x": 228, + "y": 177, + "w": 53, + "h": 46 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 220, + "w": 54, + "h": 46 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 220, + "w": 54, + "h": 46 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 111, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 111, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 166, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 166, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 221, + "y": 223, + "w": 54, + "h": 46 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 221, + "y": 223, + "w": 54, + "h": 46 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:9097bf5ffed4b93401c65aa14299faaa:d22b7c7f6e33b1453fda428e689d4529:c79e17c206de27e3b7f1ce96f7df8e51$" + } } diff --git a/public/images/pokemon/shiny/451.png b/public/images/pokemon/shiny/451.png index 4f8120ce668..69d165c9ae8 100644 Binary files a/public/images/pokemon/shiny/451.png and b/public/images/pokemon/shiny/451.png differ diff --git a/public/images/pokemon/shiny/female/275.png b/public/images/pokemon/shiny/female/275.png index c8f9a90b721..a0571493279 100644 Binary files a/public/images/pokemon/shiny/female/275.png and b/public/images/pokemon/shiny/female/275.png differ diff --git a/public/images/pokemon/variant/6706.json b/public/images/pokemon/variant/6706.json new file mode 100644 index 00000000000..2b9ead28219 --- /dev/null +++ b/public/images/pokemon/variant/6706.json @@ -0,0 +1,40 @@ +{ + "1": { + "566678": "0e6296", + "e0e4f4": "513981", + "625287": "4e4094", + "536273": "1f1233", + "988b98": "b24c86", + "9170b9": "8b69c3", + "c4cce1": "3b235c", + "e6d3e9": "f1a4c5", + "bfacc1": "da75a5", + "515f70": "197497", + "c5cee3": "63cee1", + "8e96aa": "301848", + "8b93a6": "3aa8c4", + "80737f": "8a2166", + "4b454f": "6f1357", + "36404c": "0c5474", + "b791f2": "c7a1e5" + }, + "2": { + "566678": "8e480b", + "e0e4f4": "176463", + "625287": "274159", + "536273": "02262c", + "988b98": "2a6563", + "9170b9": "2f667c", + "c4cce1": "0d484a", + "e6d3e9": "9cead8", + "bfacc1": "5db6a9", + "515f70": "a34205", + "c5cee3": "f7af58", + "8e96aa": "073338", + "8b93a6": "d27e26", + "80737f": "2b736f", + "4b454f": "194f51", + "36404c": "842401", + "b791f2": "4a9699" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/6706_2.json b/public/images/pokemon/variant/6706_2.json deleted file mode 100644 index f6bbf20116e..00000000000 --- a/public/images/pokemon/variant/6706_2.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "textures": [ - { - "image": "6706_2.png", - "format": "RGBA8888", - "size": { - "w": 82, - "h": 82 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 82, - "h": 72 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 82, - "h": 72 - }, - "frame": { - "x": 0, - "y": 0, - "w": 82, - "h": 72 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:02eb46aa66ac70df612e129b7801a85c:a77cca14b23f4f3aece64d1a82449a0f:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/6706_2.png b/public/images/pokemon/variant/6706_2.png deleted file mode 100644 index 7cf1495d1a3..00000000000 Binary files a/public/images/pokemon/variant/6706_2.png and /dev/null differ diff --git a/public/images/pokemon/variant/6706_3.json b/public/images/pokemon/variant/6706_3.json deleted file mode 100644 index 615ca90e004..00000000000 --- a/public/images/pokemon/variant/6706_3.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "textures": [ - { - "image": "6706_3.png", - "format": "RGBA8888", - "size": { - "w": 82, - "h": 82 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 82, - "h": 72 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 82, - "h": 72 - }, - "frame": { - "x": 0, - "y": 0, - "w": 82, - "h": 72 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:02eb46aa66ac70df612e129b7801a85c:a77cca14b23f4f3aece64d1a82449a0f:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/6706_3.png b/public/images/pokemon/variant/6706_3.png deleted file mode 100644 index 001cab641f1..00000000000 Binary files a/public/images/pokemon/variant/6706_3.png and /dev/null differ diff --git a/public/images/pokemon/variant/_masterlist.json b/public/images/pokemon/variant/_masterlist.json index a93fd220b1f..60ef635b862 100644 --- a/public/images/pokemon/variant/_masterlist.json +++ b/public/images/pokemon/variant/_masterlist.json @@ -3721,8 +3721,8 @@ ], "6706": [ 0, - 2, - 2 + 1, + 1 ], "6713": [ 0, @@ -7754,8 +7754,8 @@ ], "6706": [ 0, - 2, - 2 + 1, + 1 ], "6713": [ 0, @@ -8493,8 +8493,8 @@ ], "705": [ 0, - 2, - 2 + 1, + 1 ], "706": [ 0, @@ -9568,8 +9568,8 @@ ], "6706": [ 0, - 2, - 2 + 1, + 1 ], "female": {}, "back": { @@ -11095,8 +11095,8 @@ ], "6706": [ 0, - 2, - 2 + 1, + 1 ], "6713": [ 0, diff --git a/public/images/pokemon/variant/back/6706.json b/public/images/pokemon/variant/back/6706.json new file mode 100644 index 00000000000..2de5352e936 --- /dev/null +++ b/public/images/pokemon/variant/back/6706.json @@ -0,0 +1,38 @@ +{ + "1": { + "566678": "197497", + "8e96aa": "3b235c", + "929aad": "3aa8c4", + "625287": "4e4094", + "536273": "301848", + "988b98": "b24c86", + "36404c": "0c5474", + "c4cce1": "513981", + "e6d3e9": "f1a4c5", + "bfacc1": "d074a0", + "546475": "0e6296", + "c5cee3": "63cee1", + "80737f": "8a2166", + "4b454f": "6f1357", + "9170b9": "8b69c3", + "b791f2": "c7a1e5" + }, + "2": { + "566678": "a34205", + "8e96aa": "073338", + "929aad": "d27e26", + "625287": "0e3f47", + "536273": "042329", + "988b98": "2b736f", + "36404c": "842401", + "c4cce1": "0d484a", + "e6d3e9": "9cead8", + "bfacc1": "5db6a9", + "546475": "8e480b", + "c5cee3": "f7af58", + "80737f": "194f51", + "4b454f": "274159", + "9170b9": "2f667c", + "b791f2": "4a9699" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/back/6706_2.json b/public/images/pokemon/variant/back/6706_2.json deleted file mode 100644 index dccfbce60c5..00000000000 --- a/public/images/pokemon/variant/back/6706_2.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "textures": [ - { - "image": "6706_2.png", - "format": "RGBA8888", - "size": { - "w": 79, - "h": 79 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 79, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 79, - "h": 73 - }, - "frame": { - "x": 0, - "y": 0, - "w": 79, - "h": 73 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:64f7e6dfa489012922487e45ba53d557:4d24652b372939abe499497c4b6647b0:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/back/6706_2.png b/public/images/pokemon/variant/back/6706_2.png deleted file mode 100644 index e52f1af4ba5..00000000000 Binary files a/public/images/pokemon/variant/back/6706_2.png and /dev/null differ diff --git a/public/images/pokemon/variant/back/6706_3.json b/public/images/pokemon/variant/back/6706_3.json deleted file mode 100644 index 9a97ce27059..00000000000 --- a/public/images/pokemon/variant/back/6706_3.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "textures": [ - { - "image": "6706_3.png", - "format": "RGBA8888", - "size": { - "w": 79, - "h": 79 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 79, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 79, - "h": 73 - }, - "frame": { - "x": 0, - "y": 0, - "w": 79, - "h": 73 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:64f7e6dfa489012922487e45ba53d557:4d24652b372939abe499497c4b6647b0:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/back/6706_3.png b/public/images/pokemon/variant/back/6706_3.png deleted file mode 100644 index fb780d01499..00000000000 Binary files a/public/images/pokemon/variant/back/6706_3.png and /dev/null differ diff --git a/public/images/pokemon/variant/exp/6706.json b/public/images/pokemon/variant/exp/6706.json new file mode 100644 index 00000000000..2a5f8e112ee --- /dev/null +++ b/public/images/pokemon/variant/exp/6706.json @@ -0,0 +1,40 @@ +{ + "1": { + "566678": "0e6296", + "e0e4f4": "513981", + "625287": "4e4094", + "536273": "1f1233", + "988b98": "b24c86", + "36404c": "0c5474", + "c4cce1": "3b235c", + "e6d3e9": "f1a4c5", + "bfacc1": "da75a5", + "515f70": "197497", + "c5cee3": "63cee1", + "b791f2": "c7a1e5", + "8b93a6": "3aa8c4", + "80737f": "8a2166", + "4b454f": "6f1357", + "9170b9": "8b69c3", + "8e96aa": "301848" + }, + "2": { + "566678": "8e480b", + "e0e4f4": "176463", + "625287": "274159", + "536273": "02262c", + "988b98": "2a6563", + "36404c": "842401", + "c4cce1": "0d484a", + "e6d3e9": "9cead8", + "bfacc1": "5db6a9", + "515f70": "a34205", + "c5cee3": "f7af58", + "b791f2": "4a9699", + "8b93a6": "d27e26", + "80737f": "2b736f", + "4b454f": "194f51", + "9170b9": "2f667c", + "8e96aa": "073338" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/6706_2.json b/public/images/pokemon/variant/exp/6706_2.json deleted file mode 100644 index cb2ddfb1a12..00000000000 --- a/public/images/pokemon/variant/exp/6706_2.json +++ /dev/null @@ -1,2015 +0,0 @@ -{ - "textures": [ - { - "image": "6706_2.png", - "format": "RGBA8888", - "size": { - "w": 508, - "h": 508 - }, - "scale": 1, - "frames": [ - { - "filename": "0074.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 26, - "w": 59, - "h": 61 - }, - "frame": { - "x": 0, - "y": 0, - "w": 59, - "h": 61 - } - }, - { - "filename": "0073.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 26, - "y": 25, - "w": 56, - "h": 63 - }, - "frame": { - "x": 59, - "y": 0, - "w": 56, - "h": 63 - } - }, - { - "filename": "0075.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 26, - "y": 25, - "w": 56, - "h": 63 - }, - "frame": { - "x": 59, - "y": 0, - "w": 56, - "h": 63 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 115, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0084.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 115, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 29, - "y": 21, - "w": 54, - "h": 65 - }, - "frame": { - "x": 168, - "y": 0, - "w": 54, - "h": 65 - } - }, - { - "filename": "0083.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 29, - "y": 21, - "w": 54, - "h": 65 - }, - "frame": { - "x": 168, - "y": 0, - "w": 54, - "h": 65 - } - }, - { - "filename": "0066.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 222, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0082.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 222, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0067.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 55, - "h": 66 - }, - "frame": { - "x": 275, - "y": 0, - "w": 55, - "h": 66 - } - }, - { - "filename": "0081.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 55, - "h": 66 - }, - "frame": { - "x": 275, - "y": 0, - "w": 55, - "h": 66 - } - }, - { - "filename": "0072.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 23, - "w": 54, - "h": 66 - }, - "frame": { - "x": 330, - "y": 0, - "w": 54, - "h": 66 - } - }, - { - "filename": "0076.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 23, - "w": 54, - "h": 66 - }, - "frame": { - "x": 330, - "y": 0, - "w": 54, - "h": 66 - } - }, - { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 54, - "h": 67 - }, - "frame": { - "x": 384, - "y": 0, - "w": 54, - "h": 67 - } - }, - { - "filename": "0085.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 54, - "h": 67 - }, - "frame": { - "x": 384, - "y": 0, - "w": 54, - "h": 67 - } - }, - { - "filename": "0062.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 21, - "w": 52, - "h": 68 - }, - "frame": { - "x": 438, - "y": 0, - "w": 52, - "h": 68 - } - }, - { - "filename": "0086.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 21, - "w": 52, - "h": 68 - }, - "frame": { - "x": 438, - "y": 0, - "w": 52, - "h": 68 - } - }, - { - "filename": "0068.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 68 - }, - "frame": { - "x": 0, - "y": 61, - "w": 53, - "h": 68 - } - }, - { - "filename": "0080.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 68 - }, - "frame": { - "x": 0, - "y": 61, - "w": 53, - "h": 68 - } - }, - { - "filename": "0071.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 18, - "y": 22, - "w": 52, - "h": 68 - }, - "frame": { - "x": 53, - "y": 63, - "w": 52, - "h": 68 - } - }, - { - "filename": "0077.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 18, - "y": 22, - "w": 52, - "h": 68 - }, - "frame": { - "x": 53, - "y": 63, - "w": 52, - "h": 68 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 19, - "y": 21, - "w": 55, - "h": 69 - }, - "frame": { - "x": 105, - "y": 65, - "w": 55, - "h": 69 - } - }, - { - "filename": "0088.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 19, - "y": 21, - "w": 55, - "h": 69 - }, - "frame": { - "x": 105, - "y": 65, - "w": 55, - "h": 69 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 160, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0087.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 160, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0069.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 16, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 213, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0079.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 16, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 213, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0070.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 15, - "y": 21, - "w": 52, - "h": 70 - }, - "frame": { - "x": 266, - "y": 66, - "w": 52, - "h": 70 - } - }, - { - "filename": "0078.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 15, - "y": 21, - "w": 52, - "h": 70 - }, - "frame": { - "x": 266, - "y": 66, - "w": 52, - "h": 70 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 18, - "w": 87, - "h": 71 - }, - "frame": { - "x": 405, - "y": 68, - "w": 87, - "h": 71 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 19, - "w": 86, - "h": 72 - }, - "frame": { - "x": 0, - "y": 131, - "w": 86, - "h": 72 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 74, - "h": 80 - }, - "frame": { - "x": 434, - "y": 139, - "w": 74, - "h": 80 - } - }, - { - "filename": "0089.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 74, - "h": 80 - }, - "frame": { - "x": 434, - "y": 139, - "w": 74, - "h": 80 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 72 - }, - "frame": { - "x": 0, - "y": 203, - "w": 85, - "h": 72 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 13, - "w": 83, - "h": 72 - }, - "frame": { - "x": 85, - "y": 206, - "w": 83, - "h": 72 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 18, - "w": 84, - "h": 73 - }, - "frame": { - "x": 168, - "y": 206, - "w": 84, - "h": 73 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 252, - "y": 210, - "w": 85, - "h": 73 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 423, - "y": 219, - "w": 85, - "h": 73 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 423, - "y": 219, - "w": 85, - "h": 73 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 4, - "w": 81, - "h": 74 - }, - "frame": { - "x": 171, - "y": 279, - "w": 81, - "h": 74 - } - }, - { - "filename": "0095.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 4, - "w": 81, - "h": 74 - }, - "frame": { - "x": 171, - "y": 279, - "w": 81, - "h": 74 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 16, - "w": 84, - "h": 74 - }, - "frame": { - "x": 252, - "y": 283, - "w": 84, - "h": 74 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 16, - "w": 84, - "h": 74 - }, - "frame": { - "x": 252, - "y": 283, - "w": 84, - "h": 74 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 336, - "y": 284, - "w": 86, - "h": 73 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 336, - "y": 284, - "w": 86, - "h": 73 - } - }, - { - "filename": "0054.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 422, - "y": 292, - "w": 80, - "h": 74 - } - }, - { - "filename": "0094.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 422, - "y": 292, - "w": 80, - "h": 74 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 79, - "h": 74 - }, - "frame": { - "x": 0, - "y": 349, - "w": 79, - "h": 74 - } - }, - { - "filename": "0093.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 79, - "h": 74 - }, - "frame": { - "x": 0, - "y": 349, - "w": 79, - "h": 74 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 79, - "y": 352, - "w": 80, - "h": 74 - } - }, - { - "filename": "0092.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 79, - "y": 352, - "w": 80, - "h": 74 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 79, - "h": 75 - }, - "frame": { - "x": 0, - "y": 423, - "w": 79, - "h": 75 - } - }, - { - "filename": "0091.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 79, - "h": 75 - }, - "frame": { - "x": 0, - "y": 423, - "w": 79, - "h": 75 - } - }, - { - "filename": "0058.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 78, - "h": 75 - }, - "frame": { - "x": 79, - "y": 426, - "w": 78, - "h": 75 - } - }, - { - "filename": "0090.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 78, - "h": 75 - }, - "frame": { - "x": 79, - "y": 426, - "w": 78, - "h": 75 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 85, - "h": 75 - }, - "frame": { - "x": 157, - "y": 428, - "w": 85, - "h": 75 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:62a4a665074efb5def1545546995dc5b:de2788ebeab6b42f331926f332da5125:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/6706_2.png b/public/images/pokemon/variant/exp/6706_2.png deleted file mode 100644 index 7e7dfa8e05a..00000000000 Binary files a/public/images/pokemon/variant/exp/6706_2.png and /dev/null differ diff --git a/public/images/pokemon/variant/exp/6706_3.json b/public/images/pokemon/variant/exp/6706_3.json deleted file mode 100644 index 8c9b16b80ab..00000000000 --- a/public/images/pokemon/variant/exp/6706_3.json +++ /dev/null @@ -1,2015 +0,0 @@ -{ - "textures": [ - { - "image": "6706_3.png", - "format": "RGBA8888", - "size": { - "w": 508, - "h": 508 - }, - "scale": 1, - "frames": [ - { - "filename": "0074.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 26, - "w": 59, - "h": 61 - }, - "frame": { - "x": 0, - "y": 0, - "w": 59, - "h": 61 - } - }, - { - "filename": "0073.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 26, - "y": 25, - "w": 56, - "h": 63 - }, - "frame": { - "x": 59, - "y": 0, - "w": 56, - "h": 63 - } - }, - { - "filename": "0075.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 26, - "y": 25, - "w": 56, - "h": 63 - }, - "frame": { - "x": 59, - "y": 0, - "w": 56, - "h": 63 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 115, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0084.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 115, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 29, - "y": 21, - "w": 54, - "h": 65 - }, - "frame": { - "x": 168, - "y": 0, - "w": 54, - "h": 65 - } - }, - { - "filename": "0083.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 29, - "y": 21, - "w": 54, - "h": 65 - }, - "frame": { - "x": 168, - "y": 0, - "w": 54, - "h": 65 - } - }, - { - "filename": "0066.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 222, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0082.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 222, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0067.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 55, - "h": 66 - }, - "frame": { - "x": 275, - "y": 0, - "w": 55, - "h": 66 - } - }, - { - "filename": "0081.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 55, - "h": 66 - }, - "frame": { - "x": 275, - "y": 0, - "w": 55, - "h": 66 - } - }, - { - "filename": "0072.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 23, - "w": 54, - "h": 66 - }, - "frame": { - "x": 330, - "y": 0, - "w": 54, - "h": 66 - } - }, - { - "filename": "0076.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 23, - "w": 54, - "h": 66 - }, - "frame": { - "x": 330, - "y": 0, - "w": 54, - "h": 66 - } - }, - { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 54, - "h": 67 - }, - "frame": { - "x": 384, - "y": 0, - "w": 54, - "h": 67 - } - }, - { - "filename": "0085.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 54, - "h": 67 - }, - "frame": { - "x": 384, - "y": 0, - "w": 54, - "h": 67 - } - }, - { - "filename": "0062.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 21, - "w": 52, - "h": 68 - }, - "frame": { - "x": 438, - "y": 0, - "w": 52, - "h": 68 - } - }, - { - "filename": "0086.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 21, - "w": 52, - "h": 68 - }, - "frame": { - "x": 438, - "y": 0, - "w": 52, - "h": 68 - } - }, - { - "filename": "0068.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 68 - }, - "frame": { - "x": 0, - "y": 61, - "w": 53, - "h": 68 - } - }, - { - "filename": "0080.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 68 - }, - "frame": { - "x": 0, - "y": 61, - "w": 53, - "h": 68 - } - }, - { - "filename": "0071.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 18, - "y": 22, - "w": 52, - "h": 68 - }, - "frame": { - "x": 53, - "y": 63, - "w": 52, - "h": 68 - } - }, - { - "filename": "0077.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 18, - "y": 22, - "w": 52, - "h": 68 - }, - "frame": { - "x": 53, - "y": 63, - "w": 52, - "h": 68 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 19, - "y": 21, - "w": 55, - "h": 69 - }, - "frame": { - "x": 105, - "y": 65, - "w": 55, - "h": 69 - } - }, - { - "filename": "0088.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 19, - "y": 21, - "w": 55, - "h": 69 - }, - "frame": { - "x": 105, - "y": 65, - "w": 55, - "h": 69 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 160, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0087.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 160, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0069.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 16, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 213, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0079.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 16, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 213, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0070.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 15, - "y": 21, - "w": 52, - "h": 70 - }, - "frame": { - "x": 266, - "y": 66, - "w": 52, - "h": 70 - } - }, - { - "filename": "0078.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 15, - "y": 21, - "w": 52, - "h": 70 - }, - "frame": { - "x": 266, - "y": 66, - "w": 52, - "h": 70 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 18, - "w": 87, - "h": 71 - }, - "frame": { - "x": 405, - "y": 68, - "w": 87, - "h": 71 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 19, - "w": 86, - "h": 72 - }, - "frame": { - "x": 0, - "y": 131, - "w": 86, - "h": 72 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 74, - "h": 80 - }, - "frame": { - "x": 434, - "y": 139, - "w": 74, - "h": 80 - } - }, - { - "filename": "0089.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 74, - "h": 80 - }, - "frame": { - "x": 434, - "y": 139, - "w": 74, - "h": 80 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 72 - }, - "frame": { - "x": 0, - "y": 203, - "w": 85, - "h": 72 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 13, - "w": 83, - "h": 72 - }, - "frame": { - "x": 85, - "y": 206, - "w": 83, - "h": 72 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 18, - "w": 84, - "h": 73 - }, - "frame": { - "x": 168, - "y": 206, - "w": 84, - "h": 73 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 252, - "y": 210, - "w": 85, - "h": 73 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 423, - "y": 219, - "w": 85, - "h": 73 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 423, - "y": 219, - "w": 85, - "h": 73 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 4, - "w": 81, - "h": 74 - }, - "frame": { - "x": 171, - "y": 279, - "w": 81, - "h": 74 - } - }, - { - "filename": "0095.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 4, - "w": 81, - "h": 74 - }, - "frame": { - "x": 171, - "y": 279, - "w": 81, - "h": 74 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 16, - "w": 84, - "h": 74 - }, - "frame": { - "x": 252, - "y": 283, - "w": 84, - "h": 74 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 16, - "w": 84, - "h": 74 - }, - "frame": { - "x": 252, - "y": 283, - "w": 84, - "h": 74 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 336, - "y": 284, - "w": 86, - "h": 73 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 336, - "y": 284, - "w": 86, - "h": 73 - } - }, - { - "filename": "0054.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 422, - "y": 292, - "w": 80, - "h": 74 - } - }, - { - "filename": "0094.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 422, - "y": 292, - "w": 80, - "h": 74 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 79, - "h": 74 - }, - "frame": { - "x": 0, - "y": 349, - "w": 79, - "h": 74 - } - }, - { - "filename": "0093.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 79, - "h": 74 - }, - "frame": { - "x": 0, - "y": 349, - "w": 79, - "h": 74 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 79, - "y": 352, - "w": 80, - "h": 74 - } - }, - { - "filename": "0092.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 79, - "y": 352, - "w": 80, - "h": 74 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 79, - "h": 75 - }, - "frame": { - "x": 0, - "y": 423, - "w": 79, - "h": 75 - } - }, - { - "filename": "0091.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 79, - "h": 75 - }, - "frame": { - "x": 0, - "y": 423, - "w": 79, - "h": 75 - } - }, - { - "filename": "0058.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 78, - "h": 75 - }, - "frame": { - "x": 79, - "y": 426, - "w": 78, - "h": 75 - } - }, - { - "filename": "0090.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 78, - "h": 75 - }, - "frame": { - "x": 79, - "y": 426, - "w": 78, - "h": 75 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 85, - "h": 75 - }, - "frame": { - "x": 157, - "y": 428, - "w": 85, - "h": 75 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:62a4a665074efb5def1545546995dc5b:de2788ebeab6b42f331926f332da5125:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/6706_3.png b/public/images/pokemon/variant/exp/6706_3.png deleted file mode 100644 index 3ad44f4bbf5..00000000000 Binary files a/public/images/pokemon/variant/exp/6706_3.png and /dev/null differ diff --git a/public/images/pokemon/variant/exp/705.json b/public/images/pokemon/variant/exp/705.json new file mode 100644 index 00000000000..a29b8f124dc --- /dev/null +++ b/public/images/pokemon/variant/exp/705.json @@ -0,0 +1,33 @@ +{ + "1": { + "101010":"101010", + "4d454d":"8a2166", + "807380":"b93f84", + "bfacbf":"e56ca6", + "f2daf2":"fbb3d2", + "665980":"4e4094", + "8f7db3":"8b69c3", + "b8a1e5":"c7a1e5", + "4d993d":"aa6a00", + "66cc52":"ffd047", + "4e9c3e":"0c5474", + "67cf53":"3aa8c4", + "b6f2aa":"63cee1" + }, + "2": { + "101010":"101010", + "4d454d":"194f51", + "807380":"2b736f", + "bfacbf":"5db6a9", + "f2daf2":"9cead8", + "665980":"274159", + "8f7db3":"2f667c", + "b8a1e5":"4a9699", + "4d993d":"007d61", + "66cc52":"49ffbf", + "4e9c3e":"842401", + "67cf53":"a34205", + "b6f2aa":"d27e26" + } +} + diff --git a/public/images/pokemon/variant/exp/705_2.json b/public/images/pokemon/variant/exp/705_2.json deleted file mode 100644 index bf9fd104c5d..00000000000 --- a/public/images/pokemon/variant/exp/705_2.json +++ /dev/null @@ -1,272 +0,0 @@ -{ - "textures": [ - { - "image": "705_2.png", - "format": "RGBA8888", - "size": { - "w": 154, - "h": 154 - }, - "scale": 1, - "frames": [ - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 46, - "h": 58 - }, - "frame": { - "x": 0, - "y": 0, - "w": 46, - "h": 58 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 46, - "h": 58 - }, - "frame": { - "x": 0, - "y": 0, - "w": 46, - "h": 58 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 46, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 46, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 91, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 42, - "h": 58 - }, - "frame": { - "x": 0, - "y": 58, - "w": 42, - "h": 58 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 42, - "h": 58 - }, - "frame": { - "x": 0, - "y": 58, - "w": 42, - "h": 58 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 41, - "h": 58 - }, - "frame": { - "x": 42, - "y": 58, - "w": 41, - "h": 58 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 41, - "h": 58 - }, - "frame": { - "x": 42, - "y": 58, - "w": 41, - "h": 58 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 36, - "h": 58 - }, - "frame": { - "x": 83, - "y": 58, - "w": 36, - "h": 58 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 36, - "h": 58 - }, - "frame": { - "x": 83, - "y": 58, - "w": 36, - "h": 58 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 35, - "h": 58 - }, - "frame": { - "x": 119, - "y": 58, - "w": 35, - "h": 58 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:4bf155254b23c88780e7eee282256589:82bb727988054c3064e203b6908ff464:6b57e983626c7fc9144ab67f30c66814$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/705_2.png b/public/images/pokemon/variant/exp/705_2.png deleted file mode 100644 index 8256ebc7fdb..00000000000 Binary files a/public/images/pokemon/variant/exp/705_2.png and /dev/null differ diff --git a/public/images/pokemon/variant/exp/705_3.json b/public/images/pokemon/variant/exp/705_3.json deleted file mode 100644 index 199d7bc9c3e..00000000000 --- a/public/images/pokemon/variant/exp/705_3.json +++ /dev/null @@ -1,272 +0,0 @@ -{ - "textures": [ - { - "image": "705_3.png", - "format": "RGBA8888", - "size": { - "w": 154, - "h": 154 - }, - "scale": 1, - "frames": [ - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 46, - "h": 58 - }, - "frame": { - "x": 0, - "y": 0, - "w": 46, - "h": 58 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 46, - "h": 58 - }, - "frame": { - "x": 0, - "y": 0, - "w": 46, - "h": 58 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 46, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 46, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 91, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 42, - "h": 58 - }, - "frame": { - "x": 0, - "y": 58, - "w": 42, - "h": 58 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 42, - "h": 58 - }, - "frame": { - "x": 0, - "y": 58, - "w": 42, - "h": 58 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 41, - "h": 58 - }, - "frame": { - "x": 42, - "y": 58, - "w": 41, - "h": 58 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 41, - "h": 58 - }, - "frame": { - "x": 42, - "y": 58, - "w": 41, - "h": 58 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 36, - "h": 58 - }, - "frame": { - "x": 83, - "y": 58, - "w": 36, - "h": 58 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 36, - "h": 58 - }, - "frame": { - "x": 83, - "y": 58, - "w": 36, - "h": 58 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 35, - "h": 58 - }, - "frame": { - "x": 119, - "y": 58, - "w": 35, - "h": 58 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:4bf155254b23c88780e7eee282256589:82bb727988054c3064e203b6908ff464:6b57e983626c7fc9144ab67f30c66814$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/705_3.png b/public/images/pokemon/variant/exp/705_3.png deleted file mode 100644 index 66b43956bdf..00000000000 Binary files a/public/images/pokemon/variant/exp/705_3.png and /dev/null differ diff --git a/public/images/pokemon/variant/exp/back/6706.json b/public/images/pokemon/variant/exp/back/6706.json new file mode 100644 index 00000000000..2de5352e936 --- /dev/null +++ b/public/images/pokemon/variant/exp/back/6706.json @@ -0,0 +1,38 @@ +{ + "1": { + "566678": "197497", + "8e96aa": "3b235c", + "929aad": "3aa8c4", + "625287": "4e4094", + "536273": "301848", + "988b98": "b24c86", + "36404c": "0c5474", + "c4cce1": "513981", + "e6d3e9": "f1a4c5", + "bfacc1": "d074a0", + "546475": "0e6296", + "c5cee3": "63cee1", + "80737f": "8a2166", + "4b454f": "6f1357", + "9170b9": "8b69c3", + "b791f2": "c7a1e5" + }, + "2": { + "566678": "a34205", + "8e96aa": "073338", + "929aad": "d27e26", + "625287": "0e3f47", + "536273": "042329", + "988b98": "2b736f", + "36404c": "842401", + "c4cce1": "0d484a", + "e6d3e9": "9cead8", + "bfacc1": "5db6a9", + "546475": "8e480b", + "c5cee3": "f7af58", + "80737f": "194f51", + "4b454f": "274159", + "9170b9": "2f667c", + "b791f2": "4a9699" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/6706_2.json b/public/images/pokemon/variant/exp/back/6706_2.json deleted file mode 100644 index 5c916aeb664..00000000000 --- a/public/images/pokemon/variant/exp/back/6706_2.json +++ /dev/null @@ -1,776 +0,0 @@ -{ - "textures": [ - { - "image": "6706_2.png", - "format": "RGBA8888", - "size": { - "w": 358, - "h": 358 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 84, - "h": 69 - }, - "frame": { - "x": 0, - "y": 0, - "w": 84, - "h": 69 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 84, - "h": 69 - }, - "frame": { - "x": 0, - "y": 0, - "w": 84, - "h": 69 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 84, - "y": 0, - "w": 83, - "h": 72 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 84, - "y": 0, - "w": 83, - "h": 72 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 0, - "y": 69, - "w": 83, - "h": 72 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 167, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 167, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 250, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 250, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 167, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 167, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 83, - "y": 72, - "w": 82, - "h": 73 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 83, - "y": 72, - "w": 82, - "h": 73 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 249, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 249, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 0, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 0, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 0, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 0, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 81, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 81, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 81, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 81, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 165, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 165, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 247, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 247, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 162, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 162, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 3, - "w": 81, - "h": 70 - }, - "frame": { - "x": 162, - "y": 288, - "w": 81, - "h": 70 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 3, - "w": 81, - "h": 70 - }, - "frame": { - "x": 162, - "y": 288, - "w": 81, - "h": 70 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 242, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 242, - "y": 215, - "w": 80, - "h": 73 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:5d65e2c5a6a97b7c7014a175ce3592af:3255e87f637a475d82734fc7d93baf71:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/6706_2.png b/public/images/pokemon/variant/exp/back/6706_2.png deleted file mode 100644 index cb793478420..00000000000 Binary files a/public/images/pokemon/variant/exp/back/6706_2.png and /dev/null differ diff --git a/public/images/pokemon/variant/exp/back/6706_3.json b/public/images/pokemon/variant/exp/back/6706_3.json deleted file mode 100644 index 3bb1dc426b2..00000000000 --- a/public/images/pokemon/variant/exp/back/6706_3.json +++ /dev/null @@ -1,776 +0,0 @@ -{ - "textures": [ - { - "image": "6706_3.png", - "format": "RGBA8888", - "size": { - "w": 358, - "h": 358 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 84, - "h": 69 - }, - "frame": { - "x": 0, - "y": 0, - "w": 84, - "h": 69 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 84, - "h": 69 - }, - "frame": { - "x": 0, - "y": 0, - "w": 84, - "h": 69 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 84, - "y": 0, - "w": 83, - "h": 72 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 84, - "y": 0, - "w": 83, - "h": 72 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 0, - "y": 69, - "w": 83, - "h": 72 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 167, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 167, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 250, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 250, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 167, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 167, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 83, - "y": 72, - "w": 82, - "h": 73 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 83, - "y": 72, - "w": 82, - "h": 73 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 249, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 249, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 0, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 0, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 0, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 0, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 81, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 81, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 81, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 81, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 165, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 165, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 247, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 247, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 162, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 162, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 3, - "w": 81, - "h": 70 - }, - "frame": { - "x": 162, - "y": 288, - "w": 81, - "h": 70 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 3, - "w": 81, - "h": 70 - }, - "frame": { - "x": 162, - "y": 288, - "w": 81, - "h": 70 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 242, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 242, - "y": 215, - "w": 80, - "h": 73 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:5d65e2c5a6a97b7c7014a175ce3592af:3255e87f637a475d82734fc7d93baf71:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/6706_3.png b/public/images/pokemon/variant/exp/back/6706_3.png deleted file mode 100644 index 6390c20799f..00000000000 Binary files a/public/images/pokemon/variant/exp/back/6706_3.png and /dev/null differ diff --git a/public/images/statuses_it.png b/public/images/statuses_it.png index d372b989be9..af3107018f8 100644 Binary files a/public/images/statuses_it.png and b/public/images/statuses_it.png differ diff --git a/public/images/types_it.png b/public/images/types_it.png index 8b644f1041c..3be03aeea68 100644 Binary files a/public/images/types_it.png and b/public/images/types_it.png differ diff --git a/public/locales b/public/locales index b44ee217378..fc4a1effd51 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit b44ee2173788018ffd5dc6b7b7fa159be5b9d514 +Subproject commit fc4a1effd5170def3c8314208a52cd0d8e6913ef diff --git a/src/battle-scene.ts b/src/battle-scene.ts index cc6934f20d1..a21e1b09342 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -4,7 +4,7 @@ import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "#app/utils"; -import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; +import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; import { PokeballType } from "#app/data/pokeball"; import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims"; import { Phase } from "#app/phase"; @@ -86,7 +86,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; -import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; +import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, mysteryEncountersByBiome } from "#app/data/mystery-encounters/mystery-encounters"; import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -95,6 +95,7 @@ import { ExpPhase } from "#app/phases/exp-phase"; import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { ExpGainsSpeed } from "#enums/exp-gains-speed"; +import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -789,7 +790,7 @@ export default class BattleScene extends SceneBase { } getEnemyParty(): EnemyPokemon[] { - return this.currentBattle?.enemyParty || []; + return this.currentBattle?.enemyParty ?? []; } getEnemyPokemon(): EnemyPokemon | undefined { @@ -1190,10 +1191,7 @@ export default class BattleScene extends SceneBase { if (trainerConfigs[trainerType].doubleOnly) { doubleTrainer = true; } else if (trainerConfigs[trainerType].hasDouble) { - const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8); - this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); - playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance)); - doubleTrainer = !Utils.randSeedInt(doubleChance.value); + doubleTrainer = !Utils.randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); // Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance if (trainerConfigs[trainerType].trainerTypeDouble && ![ TrainerType.TATE, TrainerType.LIZA ].includes(trainerType)) { doubleTrainer = false; @@ -1206,12 +1204,10 @@ export default class BattleScene extends SceneBase { // Check for mystery encounter // Can only occur in place of a standard (non-boss) wild battle, waves 10-180 - if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType) || newBattleType === BattleType.MYSTERY_ENCOUNTER) { + if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex) || newBattleType === BattleType.MYSTERY_ENCOUNTER) { newBattleType = BattleType.MYSTERY_ENCOUNTER; - // Reset base spawn weight + // Reset to base spawn weight this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT; - } else if (newBattleType === BattleType.WILD) { - this.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS; } } @@ -1358,69 +1354,69 @@ export default class BattleScene extends SceneBase { } switch (species.speciesId) { - case Species.UNOWN: - case Species.SHELLOS: - case Species.GASTRODON: - case Species.BASCULIN: - case Species.DEERLING: - case Species.SAWSBUCK: - case Species.FROAKIE: - case Species.FROGADIER: - case Species.SCATTERBUG: - case Species.SPEWPA: - case Species.VIVILLON: - case Species.FLABEBE: - case Species.FLOETTE: - case Species.FLORGES: - case Species.FURFROU: - case Species.PUMPKABOO: - case Species.GOURGEIST: - case Species.ORICORIO: - case Species.MAGEARNA: - case Species.ZARUDE: - case Species.SQUAWKABILLY: - case Species.TATSUGIRI: - case Species.PALDEA_TAUROS: - return Utils.randSeedInt(species.forms.length); - case Species.PIKACHU: - return Utils.randSeedInt(8); - case Species.EEVEE: - return Utils.randSeedInt(2); - case Species.GRENINJA: - return Utils.randSeedInt(2); - case Species.ZYGARDE: - return Utils.randSeedInt(3); - case Species.MINIOR: - return Utils.randSeedInt(6); - case Species.ALCREMIE: - return Utils.randSeedInt(9); - case Species.MEOWSTIC: - case Species.INDEEDEE: - case Species.BASCULEGION: - case Species.OINKOLOGNE: - return gender === Gender.FEMALE ? 1 : 0; - case Species.TOXTRICITY: - const lowkeyNatures = [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]; - if (nature !== undefined && lowkeyNatures.indexOf(nature) > -1) { - return 1; - } - return 0; - case Species.GIMMIGHOUL: - // Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs - if (this.gameMode.hasMysteryEncounters) { - return 1; // Wandering form - } else { + case Species.UNOWN: + case Species.SHELLOS: + case Species.GASTRODON: + case Species.BASCULIN: + case Species.DEERLING: + case Species.SAWSBUCK: + case Species.FROAKIE: + case Species.FROGADIER: + case Species.SCATTERBUG: + case Species.SPEWPA: + case Species.VIVILLON: + case Species.FLABEBE: + case Species.FLOETTE: + case Species.FLORGES: + case Species.FURFROU: + case Species.PUMPKABOO: + case Species.GOURGEIST: + case Species.ORICORIO: + case Species.MAGEARNA: + case Species.ZARUDE: + case Species.SQUAWKABILLY: + case Species.TATSUGIRI: + case Species.PALDEA_TAUROS: return Utils.randSeedInt(species.forms.length); - } + case Species.PIKACHU: + return Utils.randSeedInt(8); + case Species.EEVEE: + return Utils.randSeedInt(2); + case Species.GRENINJA: + return Utils.randSeedInt(2); + case Species.ZYGARDE: + return Utils.randSeedInt(4); + case Species.MINIOR: + return Utils.randSeedInt(6); + case Species.ALCREMIE: + return Utils.randSeedInt(9); + case Species.MEOWSTIC: + case Species.INDEEDEE: + case Species.BASCULEGION: + case Species.OINKOLOGNE: + return gender === Gender.FEMALE ? 1 : 0; + case Species.TOXTRICITY: + const lowkeyNatures = [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]; + if (nature !== undefined && lowkeyNatures.indexOf(nature) > -1) { + return 1; + } + return 0; + case Species.GIMMIGHOUL: + // Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs + if (this.gameMode.hasMysteryEncounters) { + return 1; // Wandering form + } else { + return Utils.randSeedInt(species.forms.length); + } } if (ignoreArena) { switch (species.speciesId) { - case Species.BURMY: - case Species.WORMADAM: - case Species.ROTOM: - case Species.LYCANROC: - return Utils.randSeedInt(species.forms.length); + case Species.BURMY: + case Species.WORMADAM: + case Species.ROTOM: + case Species.LYCANROC: + return Utils.randSeedInt(species.forms.length); } return 0; } @@ -1885,17 +1881,17 @@ export default class BattleScene extends SceneBase { const soundDetails = sound.key.split("/"); switch (soundDetails[0]) { - case "battle_anims": - case "cry": - if (soundDetails[1].startsWith("PRSFX- ")) { - sound.setVolume(this.masterVolume * this.fieldVolume * 0.5); - } else { - sound.setVolume(this.masterVolume * this.fieldVolume); - } - break; - case "se": - case "ui": - sound.setVolume(this.masterVolume * this.seVolume); + case "battle_anims": + case "cry": + if (soundDetails[1].startsWith("PRSFX- ")) { + sound.setVolume(this.masterVolume * this.fieldVolume * 0.5); + } else { + sound.setVolume(this.masterVolume * this.fieldVolume); + } + break; + case "se": + case "ui": + sound.setVolume(this.masterVolume * this.seVolume); } } } @@ -1935,31 +1931,31 @@ export default class BattleScene extends SceneBase { const keyDetails = key.split("/"); config["volume"] = config["volume"] ?? 1; switch (keyDetails[0]) { - case "level_up_fanfare": - case "item_fanfare": - case "minor_fanfare": - case "heal": - case "evolution": - case "evolution_fanfare": + case "level_up_fanfare": + case "item_fanfare": + case "minor_fanfare": + case "heal": + case "evolution": + case "evolution_fanfare": // These sounds are loaded in as BGM, but played as sound effects // When these sounds are updated in updateVolume(), they are treated as BGM however because they are placed in the BGM Cache through being called by playSoundWithoutBGM() - config["volume"] *= (this.masterVolume * this.bgmVolume); - break; - case "battle_anims": - case "cry": - config["volume"] *= (this.masterVolume * this.fieldVolume); - //PRSFX sound files are unusually loud - if (keyDetails[1].startsWith("PRSFX- ")) { - config["volume"] *= 0.5; - } - break; - case "ui": + config["volume"] *= (this.masterVolume * this.bgmVolume); + break; + case "battle_anims": + case "cry": + config["volume"] *= (this.masterVolume * this.fieldVolume); + //PRSFX sound files are unusually loud + if (keyDetails[1].startsWith("PRSFX- ")) { + config["volume"] *= 0.5; + } + break; + case "ui": //As of, right now this applies to the "select", "menu_open", "error" sound effects - config["volume"] *= (this.masterVolume * this.uiVolume); - break; - case "se": - config["volume"] *= (this.masterVolume * this.seVolume); - break; + config["volume"] *= (this.masterVolume * this.uiVolume); + break; + case "se": + config["volume"] *= (this.masterVolume * this.seVolume); + break; } this.sound.play(key, config); return this.sound.get(key) as AnySound; @@ -1988,208 +1984,208 @@ export default class BattleScene extends SceneBase { getBgmLoopPoint(bgmName: string): number { switch (bgmName) { - case "battle_kanto_champion": //B2W2 Kanto Champion Battle - return 13.950; - case "battle_johto_champion": //B2W2 Johto Champion Battle - return 23.498; - case "battle_hoenn_champion_g5": //B2W2 Hoenn Champion Battle - return 11.328; - case "battle_hoenn_champion_g6": //ORAS Hoenn Champion Battle - return 11.762; - case "battle_sinnoh_champion": //B2W2 Sinnoh Champion Battle - return 12.235; - case "battle_champion_alder": //BW Unova Champion Battle - return 27.653; - case "battle_champion_iris": //B2W2 Unova Champion Battle - return 10.145; - case "battle_kalos_champion": //XY Kalos Champion Battle - return 10.380; - case "battle_alola_champion": //USUM Alola Champion Battle - return 13.025; - case "battle_galar_champion": //SWSH Galar Champion Battle - return 61.635; - case "battle_champion_geeta": //SV Champion Geeta Battle - return 37.447; - case "battle_champion_nemona": //SV Champion Nemona Battle - return 14.914; - case "battle_champion_kieran": //SV Champion Kieran Battle - return 7.206; - case "battle_hoenn_elite": //ORAS Elite Four Battle - return 11.350; - case "battle_unova_elite": //BW Elite Four Battle - return 17.730; - case "battle_kalos_elite": //XY Elite Four Battle - return 12.340; - case "battle_alola_elite": //SM Elite Four Battle - return 19.212; - case "battle_galar_elite": //SWSH League Tournament Battle - return 164.069; - case "battle_paldea_elite": //SV Elite Four Battle - return 12.770; - case "battle_bb_elite": //SV BB League Elite Four Battle - return 19.434; - case "battle_final_encounter": //PMD RTDX Rayquaza's Domain - return 19.159; - case "battle_final": //BW Ghetsis Battle - return 16.453; - case "battle_kanto_gym": //B2W2 Kanto Gym Battle - return 13.857; - case "battle_johto_gym": //B2W2 Johto Gym Battle - return 12.911; - case "battle_hoenn_gym": //B2W2 Hoenn Gym Battle - return 12.379; - case "battle_sinnoh_gym": //B2W2 Sinnoh Gym Battle - return 13.122; - case "battle_unova_gym": //BW Unova Gym Battle - return 19.145; - case "battle_kalos_gym": //XY Kalos Gym Battle - return 44.810; - case "battle_galar_gym": //SWSH Galar Gym Battle - return 171.262; - case "battle_paldea_gym": //SV Paldea Gym Battle - return 127.489; - case "battle_legendary_kanto": //XY Kanto Legendary Battle - return 32.966; - case "battle_legendary_raikou": //HGSS Raikou Battle - return 12.632; - case "battle_legendary_entei": //HGSS Entei Battle - return 2.905; - case "battle_legendary_suicune": //HGSS Suicune Battle - return 12.636; - case "battle_legendary_lugia": //HGSS Lugia Battle - return 19.770; - case "battle_legendary_ho_oh": //HGSS Ho-oh Battle - return 17.668; - case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle - return 49.500; - case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle - return 21.130; - case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle - return 10.547; - case "battle_legendary_rayquaza": //ORAS Rayquaza Battle - return 10.495; - case "battle_legendary_deoxys": //ORAS Deoxys Battle - return 13.333; - case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle - return 16.887; - case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle - return 22.770; - case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle - return 16.009; - case "battle_legendary_origin_forme": //LA Origin Dialga & Palkia Battle - return 18.961; - case "battle_legendary_giratina": //ORAS Giratina Battle - return 10.451; - case "battle_legendary_arceus": //HGSS Arceus Battle - return 9.595; - case "battle_legendary_unova": //BW Unova Legendary Battle - return 13.855; - case "battle_legendary_kyurem": //BW Kyurem Battle - return 18.314; - case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle - return 18.329; - case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle - return 26.468; - case "battle_legendary_tapu": //SM Tapu Battle - return 0.000; - case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle - return 6.525; - case "battle_legendary_ub": //SM Ultra Beast Battle - return 9.818; - case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle - return 5.211; - case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle - return 10.344; - case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle - return 11.424; - case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle - return 12.503; - case "battle_legendary_calyrex": //SWSH Calyrex Battle - return 50.641; - case "battle_legendary_riders": //SWSH Ice & Shadow Rider Calyrex Battle - return 18.155; - case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle - return 0.175; - case "battle_legendary_ruinous": //SV Treasures of Ruin Battle - return 6.333; - case "battle_legendary_kor_mir": //SV Depths of Area Zero Battle - return 6.442; - case "battle_legendary_loyal_three": //SV Loyal Three Battle - return 6.500; - case "battle_legendary_ogerpon": //SV Ogerpon Battle - return 14.335; - case "battle_legendary_terapagos": //SV Terapagos Battle - return 24.377; - case "battle_legendary_pecharunt": //SV Pecharunt Battle - return 6.508; - case "battle_rival": //BW Rival Battle - return 14.110; - case "battle_rival_2": //BW N Battle - return 17.714; - case "battle_rival_3": //BW Final N Battle - return 17.586; - case "battle_trainer": //BW Trainer Battle - return 13.686; - case "battle_wild": //BW Wild Battle - return 12.703; - case "battle_wild_strong": //BW Strong Wild Battle - return 13.940; - case "end_summit": //PMD RTDX Sky Tower Summit - return 30.025; - case "battle_rocket_grunt": //HGSS Team Rocket Battle - return 12.707; - case "battle_aqua_magma_grunt": //ORAS Team Aqua & Magma Battle - return 12.062; - case "battle_galactic_grunt": //BDSP Team Galactic Battle - return 13.043; - case "battle_plasma_grunt": //BW Team Plasma Battle - return 12.974; - case "battle_flare_grunt": //XY Team Flare Battle - return 4.228; - case "battle_aether_grunt": // SM Aether Foundation Battle - return 16.00; - case "battle_skull_grunt": // SM Team Skull Battle - return 20.87; - case "battle_macro_grunt": // SWSH Trainer Battle - return 11.56; - case "battle_star_grunt": //SV Team Star Battle - return 133.362; - case "battle_galactic_admin": //BDSP Team Galactic Admin Battle - return 11.997; - case "battle_skull_admin": //SM Team Skull Admin Battle - return 15.463; - case "battle_oleana": //SWSH Oleana Battle - return 14.110; - case "battle_star_admin": //SV Team Star Boss Battle - return 9.493; - case "battle_rocket_boss": //USUM Giovanni Battle - return 9.115; - case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle - return 14.847; - case "battle_galactic_boss": //BDSP Cyrus Battle - return 106.962; - case "battle_plasma_boss": //B2W2 Ghetsis Battle - return 25.624; - case "battle_flare_boss": //XY Lysandre Battle - return 8.085; - case "battle_aether_boss": //SM Lusamine Battle - return 11.33; - case "battle_skull_boss": //SM Guzma Battle - return 13.13; - case "battle_macro_boss": //SWSH Rose Battle - return 11.42; - case "battle_star_boss": //SV Cassiopeia Battle - return 25.764; - case "mystery_encounter_gen_5_gts": // BW GTS - return 8.52; - case "mystery_encounter_gen_6_gts": // XY GTS - return 9.24; - case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff - return 4.78; - case "mystery_encounter_weird_dream": // EoS Temporal Spire - return 41.42; - case "mystery_encounter_delibirdy": // Firel Delibirdy - return 82.28; + case "battle_kanto_champion": //B2W2 Kanto Champion Battle + return 13.950; + case "battle_johto_champion": //B2W2 Johto Champion Battle + return 23.498; + case "battle_hoenn_champion_g5": //B2W2 Hoenn Champion Battle + return 11.328; + case "battle_hoenn_champion_g6": //ORAS Hoenn Champion Battle + return 11.762; + case "battle_sinnoh_champion": //B2W2 Sinnoh Champion Battle + return 12.235; + case "battle_champion_alder": //BW Unova Champion Battle + return 27.653; + case "battle_champion_iris": //B2W2 Unova Champion Battle + return 10.145; + case "battle_kalos_champion": //XY Kalos Champion Battle + return 10.380; + case "battle_alola_champion": //USUM Alola Champion Battle + return 13.025; + case "battle_galar_champion": //SWSH Galar Champion Battle + return 61.635; + case "battle_champion_geeta": //SV Champion Geeta Battle + return 37.447; + case "battle_champion_nemona": //SV Champion Nemona Battle + return 14.914; + case "battle_champion_kieran": //SV Champion Kieran Battle + return 7.206; + case "battle_hoenn_elite": //ORAS Elite Four Battle + return 11.350; + case "battle_unova_elite": //BW Elite Four Battle + return 17.730; + case "battle_kalos_elite": //XY Elite Four Battle + return 12.340; + case "battle_alola_elite": //SM Elite Four Battle + return 19.212; + case "battle_galar_elite": //SWSH League Tournament Battle + return 164.069; + case "battle_paldea_elite": //SV Elite Four Battle + return 12.770; + case "battle_bb_elite": //SV BB League Elite Four Battle + return 19.434; + case "battle_final_encounter": //PMD RTDX Rayquaza's Domain + return 19.159; + case "battle_final": //BW Ghetsis Battle + return 16.453; + case "battle_kanto_gym": //B2W2 Kanto Gym Battle + return 13.857; + case "battle_johto_gym": //B2W2 Johto Gym Battle + return 12.911; + case "battle_hoenn_gym": //B2W2 Hoenn Gym Battle + return 12.379; + case "battle_sinnoh_gym": //B2W2 Sinnoh Gym Battle + return 13.122; + case "battle_unova_gym": //BW Unova Gym Battle + return 19.145; + case "battle_kalos_gym": //XY Kalos Gym Battle + return 44.810; + case "battle_galar_gym": //SWSH Galar Gym Battle + return 171.262; + case "battle_paldea_gym": //SV Paldea Gym Battle + return 127.489; + case "battle_legendary_kanto": //XY Kanto Legendary Battle + return 32.966; + case "battle_legendary_raikou": //HGSS Raikou Battle + return 12.632; + case "battle_legendary_entei": //HGSS Entei Battle + return 2.905; + case "battle_legendary_suicune": //HGSS Suicune Battle + return 12.636; + case "battle_legendary_lugia": //HGSS Lugia Battle + return 19.770; + case "battle_legendary_ho_oh": //HGSS Ho-oh Battle + return 17.668; + case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle + return 49.500; + case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle + return 21.130; + case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle + return 10.547; + case "battle_legendary_rayquaza": //ORAS Rayquaza Battle + return 10.495; + case "battle_legendary_deoxys": //ORAS Deoxys Battle + return 13.333; + case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle + return 16.887; + case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle + return 22.770; + case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle + return 16.009; + case "battle_legendary_origin_forme": //LA Origin Dialga & Palkia Battle + return 18.961; + case "battle_legendary_giratina": //ORAS Giratina Battle + return 10.451; + case "battle_legendary_arceus": //HGSS Arceus Battle + return 9.595; + case "battle_legendary_unova": //BW Unova Legendary Battle + return 13.855; + case "battle_legendary_kyurem": //BW Kyurem Battle + return 18.314; + case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle + return 18.329; + case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle + return 26.468; + case "battle_legendary_tapu": //SM Tapu Battle + return 0.000; + case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle + return 6.525; + case "battle_legendary_ub": //SM Ultra Beast Battle + return 9.818; + case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle + return 5.211; + case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle + return 10.344; + case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle + return 11.424; + case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle + return 12.503; + case "battle_legendary_calyrex": //SWSH Calyrex Battle + return 50.641; + case "battle_legendary_riders": //SWSH Ice & Shadow Rider Calyrex Battle + return 18.155; + case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle + return 0.175; + case "battle_legendary_ruinous": //SV Treasures of Ruin Battle + return 6.333; + case "battle_legendary_kor_mir": //SV Depths of Area Zero Battle + return 6.442; + case "battle_legendary_loyal_three": //SV Loyal Three Battle + return 6.500; + case "battle_legendary_ogerpon": //SV Ogerpon Battle + return 14.335; + case "battle_legendary_terapagos": //SV Terapagos Battle + return 24.377; + case "battle_legendary_pecharunt": //SV Pecharunt Battle + return 6.508; + case "battle_rival": //BW Rival Battle + return 14.110; + case "battle_rival_2": //BW N Battle + return 17.714; + case "battle_rival_3": //BW Final N Battle + return 17.586; + case "battle_trainer": //BW Trainer Battle + return 13.686; + case "battle_wild": //BW Wild Battle + return 12.703; + case "battle_wild_strong": //BW Strong Wild Battle + return 13.940; + case "end_summit": //PMD RTDX Sky Tower Summit + return 30.025; + case "battle_rocket_grunt": //HGSS Team Rocket Battle + return 12.707; + case "battle_aqua_magma_grunt": //ORAS Team Aqua & Magma Battle + return 12.062; + case "battle_galactic_grunt": //BDSP Team Galactic Battle + return 13.043; + case "battle_plasma_grunt": //BW Team Plasma Battle + return 12.974; + case "battle_flare_grunt": //XY Team Flare Battle + return 4.228; + case "battle_aether_grunt": // SM Aether Foundation Battle + return 16.00; + case "battle_skull_grunt": // SM Team Skull Battle + return 20.87; + case "battle_macro_grunt": // SWSH Trainer Battle + return 11.56; + case "battle_star_grunt": //SV Team Star Battle + return 133.362; + case "battle_galactic_admin": //BDSP Team Galactic Admin Battle + return 11.997; + case "battle_skull_admin": //SM Team Skull Admin Battle + return 15.463; + case "battle_oleana": //SWSH Oleana Battle + return 14.110; + case "battle_star_admin": //SV Team Star Boss Battle + return 9.493; + case "battle_rocket_boss": //USUM Giovanni Battle + return 9.115; + case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle + return 14.847; + case "battle_galactic_boss": //BDSP Cyrus Battle + return 106.962; + case "battle_plasma_boss": //B2W2 Ghetsis Battle + return 25.624; + case "battle_flare_boss": //XY Lysandre Battle + return 8.085; + case "battle_aether_boss": //SM Lusamine Battle + return 11.33; + case "battle_skull_boss": //SM Guzma Battle + return 13.13; + case "battle_macro_boss": //SWSH Rose Battle + return 11.42; + case "battle_star_boss": //SV Cassiopeia Battle + return 25.764; + case "mystery_encounter_gen_5_gts": // BW GTS + return 8.52; + case "mystery_encounter_gen_6_gts": // XY GTS + return 9.24; + case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff + return 4.78; + case "mystery_encounter_weird_dream": // EoS Temporal Spire + return 41.42; + case "mystery_encounter_delibirdy": // Firel Delibirdy + return 82.28; } return 0; @@ -2316,7 +2312,10 @@ export default class BattleScene extends SceneBase { } } - this.currentPhase?.start(); + if (this.currentPhase) { + console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;"); + this.currentPhase.start(); + } } overridePhase(phase: Phase): boolean { @@ -2326,6 +2325,7 @@ export default class BattleScene extends SceneBase { this.standbyPhase = this.currentPhase; this.currentPhase = phase; + console.log(`%cStart Phase ${phase.constructor.name}`, "color:green;"); phase.start(); return true; @@ -2359,6 +2359,19 @@ export default class BattleScene extends SceneBase { return false; } + /** + * Will search for a specific phase in {@linkcode phaseQueuePrepend} via filter, and remove the first result if a match is found. + * @param phaseFilter filter function + */ + tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean { + const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter); + if (phaseIndex > -1) { + this.phaseQueuePrepend.splice(phaseIndex, 1); + return true; + } + return false; + } + /** * Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase() * @param phase {@linkcode Phase} the phase to be added @@ -2421,7 +2434,7 @@ export default class BattleScene extends SceneBase { return Math.floor(moneyValue / 10) * 10; } - addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean): Promise { + addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean, cost?: number): Promise { if (!modifier) { return Promise.resolve(false); } @@ -2478,6 +2491,8 @@ export default class BattleScene extends SceneBase { } } else if (modifier instanceof FusePokemonModifier) { args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon); + } else if (modifier instanceof RememberMoveModifier && !Utils.isNullOrUndefined(cost)) { + args.push(cost); } if (modifier.shouldApply(pokemon, ...args)) { @@ -2676,7 +2691,7 @@ export default class BattleScene extends SceneBase { } /** - * Removes all modifiers from enemy of PersistentModifier type + * Removes all modifiers from enemy pokemon of {@linkcode PersistentModifier} type */ clearEnemyModifiers(): void { const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PersistentModifier); @@ -2687,10 +2702,11 @@ export default class BattleScene extends SceneBase { } /** - * Removes all modifiers from enemy of PokemonHeldItemModifier type + * Removes all modifiers from enemy pokemon of {@linkcode PokemonHeldItemModifier} type + * @param pokemon - If specified, only removes held items from that {@linkcode Pokemon} */ - clearEnemyHeldItemModifiers(): void { - const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier); + clearEnemyHeldItemModifiers(pokemon?: Pokemon): void { + const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier && (!pokemon || m.getPokemon(this) === pokemon)); for (const m of modifiersToRemove) { this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1); } @@ -3047,7 +3063,7 @@ export default class BattleScene extends SceneBase { const pId = partyMember.id; const participated = participantIds.has(pId); if (participated && pokemonDefeated) { - partyMember.addFriendship(2); + partyMember.addFriendship(FRIENDSHIP_GAIN_FROM_BATTLE); const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier); if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this)) { machoBraceModifier.stackCount++; @@ -3117,18 +3133,26 @@ export default class BattleScene extends SceneBase { } } + /** + * Returns if a wave COULD spawn a {@linkcode MysteryEncounter}. + * Even if returns `true`, does not guarantee that a wave will actually be a ME. + * That check is made in {@linkcode BattleScene.isWaveMysteryEncounter} instead. + */ + isMysteryEncounterValidForWave(battleType: BattleType, waveIndex: number): boolean { + const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves(); + return this.gameMode.hasMysteryEncounters && battleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave; + } + /** * Determines whether a wave should randomly generate a {@linkcode MysteryEncounter}. * Currently, the only modes that MEs are allowed in are Classic and Challenge. * Additionally, MEs cannot spawn outside of waves 10-180 in those modes - * * @param newBattleType * @param waveIndex - * @param sessionDataEncounterType */ - private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean { + private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number): boolean { const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves(); - if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave) { + if (this.isMysteryEncounterValidForWave(newBattleType, waveIndex)) { // Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance; const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents; @@ -3161,13 +3185,20 @@ export default class BattleScene extends SceneBase { /** * Loads or generates a mystery encounter * @param encounterType used to load session encounter when restarting game, etc. + * @param canBypass optional boolean to indicate that the request is coming from a function that needs to access a Mystery Encounter outside of gameplay requirements * @returns */ - getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter { + getMysteryEncounter(encounterType?: MysteryEncounterType, canBypass?: boolean): MysteryEncounter { // Loading override or session encounter let encounter: MysteryEncounter | null; if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) { encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE]; + if (canBypass) { + return encounter; + } + } else if (canBypass) { + encounter = allMysteryEncounters[encounterType ?? -1]; + return encounter; } else { encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null; } diff --git a/src/data/ability.ts b/src/data/ability.ts index 43d02da1733..33f6e0522f7 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -118,6 +118,14 @@ export class Ability implements Localizable { this.nameAppend += " (N)"; return this; } + + /** + * Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case. + * @returns the ability + */ + edgeCase(): this { + return this; + } } type AbAttrApplyFunc = (attr: TAttr, passive: boolean) => boolean | Promise; @@ -634,15 +642,15 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { * Examples include: Absorb, Draining Kiss, Bitter Blade, etc. * Also displays a message to show this ability was activated. * @param pokemon {@linkcode Pokemon} with this ability - * @param passive N/A + * @param _passive N/A * @param attacker {@linkcode Pokemon} that is attacking this Pokemon * @param move {@linkcode PokemonMove} that is being used - * @param hitResult N/A - * @args N/A + * @param _hitResult N/A + * @param _args N/A * @returns true if healing should be reversed on a healing move, false otherwise. */ - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.hasAttr(HitHealAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); } @@ -669,8 +677,8 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { this.allOthers = allOthers; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return true; } @@ -707,13 +715,13 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { this.selfTarget = selfTarget; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate); + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate); const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; const damageReceived = lastAttackReceived?.damage || 0; - if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat)) { - if (!simulated ) { + if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon)) { + if (!simulated) { pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); } return true; @@ -734,8 +742,8 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag; if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) { if (!simulated) { @@ -758,8 +766,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { if (!pokemon.getTag(this.tagType) && !simulated) { pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); @@ -771,8 +779,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { } export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (hitResult < HitResult.NO_EFFECT) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { + if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return true; } @@ -787,7 +795,7 @@ export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendTypeChange", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -805,8 +813,8 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { this.terrainType = terrainType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (hitResult < HitResult.NO_EFFECT) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { + if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return pokemon.scene.arena.terrain?.terrainType !== (this.terrainType || undefined); } else { @@ -829,8 +837,9 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { this.effects = effects; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status + && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; if (simulated) { return attacker.canSetStatus(effect, true, false, pokemon); @@ -869,8 +878,8 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { this.turnCount = turnCount; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return attacker.canAddTag(this.tagType); } else { @@ -893,7 +902,11 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { this.stages = stages; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.hitsSubstitute(attacker, pokemon)) { + return false; + } + if (!simulated) { pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } @@ -901,7 +914,7 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { return true; } - getCondition(): AbAttrCondition { + override getCondition(): AbAttrCondition { return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical; } } @@ -915,8 +928,9 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { this.damageRatio = damageRatio; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) + && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); return true; @@ -925,7 +939,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendContactDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName @@ -948,8 +962,8 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { this.turns = turns; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, pokemon)) { if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) { return false; } else { @@ -963,24 +977,24 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:perishBody", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName }); } } export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { private weatherType: WeatherType; - protected condition: PokemonDefendCondition | null; + protected condition?: PokemonDefendCondition; constructor(weatherType: WeatherType, condition?: PokemonDefendCondition) { super(); this.weatherType = weatherType; - this.condition = condition ?? null; + this.condition = condition; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition !== null && !this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) { return false; } if (!pokemon.scene.arena.weather?.isImmutable()) { @@ -999,8 +1013,9 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { super(); } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) + && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { const tempAbilityId = attacker.getAbility().id; attacker.summonData.ability = pokemon.getAbility().id; @@ -1012,7 +1027,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendAbilitySwap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); } } @@ -1025,8 +1040,9 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { this.ability = ability; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) + && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { attacker.summonData.ability = this.ability; } @@ -1037,7 +1053,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendAbilityGive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName @@ -1056,8 +1072,8 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { this.chance = chance; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (attacker.getTag(BattlerTagType.DISABLED) === null) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { if (simulated) { return true; @@ -1724,17 +1740,17 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { } export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { - private condition: PokemonDefendCondition | null; + private condition?: PokemonDefendCondition; constructor(condition?: PokemonDefendCondition) { super(); - this.condition = condition ?? null; + this.condition = condition; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise { return new Promise(resolve => { - if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { + if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move)) && !move.hitsSubstitute(attacker, pokemon)) { const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; @@ -2563,24 +2579,24 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { // Clear weather only if user's ability matches the weather and no other pokemon has the ability. switch (weatherType) { - case (WeatherType.HARSH_SUN): - if (pokemon.hasAbility(Abilities.DESOLATE_LAND) + case (WeatherType.HARSH_SUN): + if (pokemon.hasAbility(Abilities.DESOLATE_LAND) && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.HEAVY_RAIN): - if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) + turnOffWeather = true; + } + break; + case (WeatherType.HEAVY_RAIN): + if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.STRONG_WINDS): - if (pokemon.hasAbility(Abilities.DELTA_STREAM) + turnOffWeather = true; + } + break; + case (WeatherType.STRONG_WINDS): + if (pokemon.hasAbility(Abilities.DELTA_STREAM) && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { - turnOffWeather = true; - } - break; + turnOffWeather = true; + } + break; } if (simulated) { @@ -4063,24 +4079,24 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr { // Clear weather only if user's ability matches the weather and no other pokemon has the ability. switch (weatherType) { - case (WeatherType.HARSH_SUN): - if (pokemon.hasAbility(Abilities.DESOLATE_LAND) + case (WeatherType.HARSH_SUN): + if (pokemon.hasAbility(Abilities.DESOLATE_LAND) && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.HEAVY_RAIN): - if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) + turnOffWeather = true; + } + break; + case (WeatherType.HEAVY_RAIN): + if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.STRONG_WINDS): - if (pokemon.hasAbility(Abilities.DELTA_STREAM) + turnOffWeather = true; + } + break; + case (WeatherType.STRONG_WINDS): + if (pokemon.hasAbility(Abilities.DELTA_STREAM) && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { - turnOffWeather = true; - } - break; + turnOffWeather = true; + } + break; } if (simulated) { @@ -4476,7 +4492,7 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { private multiplier: number; private tagType: BattlerTagType; - private recoilDamageFunc: ((pokemon: Pokemon) => number) | undefined; + private recoilDamageFunc?: ((pokemon: Pokemon) => number); private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string; constructor(condition: PokemonDefendCondition, multiplier: number, tagType: BattlerTagType, triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, recoilDamageFunc?: (pokemon: Pokemon) => number) { @@ -4492,16 +4508,16 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { * Applies the pre-defense ability to the Pokémon. * Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense form. * - * @param {Pokemon} pokemon The Pokémon with the ability. - * @param {boolean} passive n/a - * @param {Pokemon} attacker The attacking Pokémon. - * @param {PokemonMove} move The move being used. - * @param {Utils.BooleanHolder} cancelled n/a - * @param {any[]} args Additional arguments. - * @returns {boolean} Whether the immunity was applied. + * @param pokemon The Pokémon with the ability. + * @param _passive n/a + * @param attacker The attacking Pokémon. + * @param move The move being used. + * @param _cancelled n/a + * @param args Additional arguments. + * @returns `true` if the immunity was applied. */ - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { (args[0] as Utils.NumberHolder).value = this.multiplier; pokemon.removeTag(this.tagType); @@ -4517,12 +4533,12 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { /** * Gets the message triggered when the Pokémon avoids damage using the form-changing ability. - * @param {Pokemon} pokemon The Pokémon with the ability. - * @param {string} abilityName The name of the ability. - * @param {...any} args n/a - * @returns {string} The trigger message. + * @param pokemon The Pokémon with the ability. + * @param abilityName The name of the ability. + * @param _args n/a + * @returns The trigger message. */ - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return this.triggerMessageFunc(pokemon, abilityName); } } @@ -4897,8 +4913,7 @@ export function initAbilities() { .attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1) .ignorable(), new Ability(Abilities.SHIELD_DUST, 3) - .attr(IgnoreMoveEffectsAbAttr) - .partial(), + .attr(IgnoreMoveEffectsAbAttr), new Ability(Abilities.OWN_TEMPO, 3) .attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED) .attr(IntimidateImmunityAbAttr) @@ -4942,8 +4957,7 @@ export function initAbilities() { .attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPATK, 1) .ignorable(), new Ability(Abilities.SERENE_GRACE, 3) - .attr(MoveEffectChanceMultiplierAbAttr, 2) - .partial(), + .attr(MoveEffectChanceMultiplierAbAttr, 2), new Ability(Abilities.SWIFT_SWIM, 3) .attr(StatMultiplierAbAttr, Stat.SPD, 2) .condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)), @@ -5227,7 +5241,8 @@ export function initAbilities() { new Ability(Abilities.SHEER_FORCE, 5) .attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461 / 4096) .attr(MoveEffectChanceMultiplierAbAttr, 0) - .partial(), + .edgeCase() // Should disable shell bell and Meloetta's relic song transformation + .edgeCase(), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented new Ability(Abilities.CONTRARY, 5) .attr(StatStageChangeMultiplierAbAttr, -1) .ignorable(), @@ -5270,7 +5285,7 @@ export function initAbilities() { /** Rate is doubled when under sun {@link https://dex.pokemonshowdown.com/abilities/harvest} */ (pokemon) => 0.5 * (getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)(pokemon) ? 2 : 1) ) - .partial(), + .edgeCase(), // Cannot recover berries used up by fling or natural gift (unimplemented) new Ability(Abilities.TELEPATHY, 5) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move instanceof AttackMove) .ignorable(), @@ -5349,7 +5364,7 @@ export function initAbilities() { .bypassFaint(), new Ability(Abilities.VICTORY_STAR, 5) .attr(StatMultiplierAbAttr, Stat.ACC, 1.1) - .partial(), + .partial(), // Does not boost ally's accuracy new Ability(Abilities.TURBOBLAZE, 5) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTurboblaze", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(MoveAbilityBypassAbAttr), @@ -5460,7 +5475,7 @@ export function initAbilities() { .attr(UnsuppressableAbilityAbAttr) .attr(NoFusionAbilityAbAttr) .bypassFaint() - .partial(), + .partial(), // Meteor form should protect against status effects and yawn new Ability(Abilities.STAKEOUT, 7) .attr(MovePowerBoostAbAttr, (user, target, move) => user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command === Command.POKEMON, 2), new Ability(Abilities.WATER_BUBBLE, 7) @@ -5503,7 +5518,8 @@ export function initAbilities() { .attr(NoFusionAbilityAbAttr) // Add BattlerTagType.DISGUISE if the pokemon is in its disguised form .conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false) - .attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE, + .attr(FormBlockDamageAbAttr, + (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE, (pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }), (pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8)) .attr(PostBattleInitFormChangeAbAttr, () => 0) @@ -5517,19 +5533,21 @@ export function initAbilities() { .attr(UnsuppressableAbilityAbAttr) .attr(NoFusionAbilityAbAttr) .bypassFaint(), - new Ability(Abilities.POWER_CONSTRUCT, 7) // TODO: 10% Power Construct Zygarde isn't accounted for yet. If changed, update Zygarde's getSpeciesFormIndex entry accordingly - .attr(PostBattleInitFormChangeAbAttr, () => 2) - .attr(PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2) - .attr(PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2) + new Ability(Abilities.POWER_CONSTRUCT, 7) + .conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostBattleInitFormChangeAbAttr, () => 2) + .conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostBattleInitFormChangeAbAttr, () => 3) + .conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2) + .conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2) + .conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3) + .conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr) .attr(NoFusionAbilityAbAttr) - .bypassFaint() - .partial(), - new Ability(Abilities.CORROSION, 7) // TODO: Test Corrosion against Magic Bounce once it is implemented + .bypassFaint(), + new Ability(Abilities.CORROSION, 7) .attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ]) - .partial(), + .edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented), fling with toxic orb (not implemented yet), and synchronize (not fully implemented yet) new Ability(Abilities.COMATOSE, 7) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) @@ -5665,7 +5683,8 @@ export function initAbilities() { .conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0) // When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE .attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW) - .attr(FormBlockDamageAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE, + .attr(FormBlockDamageAbAttr, + (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE, (pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName })) .attr(PostBattleInitFormChangeAbAttr, () => 0) .bypassFaint() @@ -5683,7 +5702,7 @@ export function initAbilities() { new Ability(Abilities.WANDERING_SPIRIT, 8) .attr(PostDefendAbilitySwapAbAttr) .bypassFaint() - .partial(), + .edgeCase(), // interacts incorrectly with rock head. It's meant to switch abilities before recoil would apply so that a pokemon with rock head would lose rock head first and still take the recoil new Ability(Abilities.GORILLA_TACTICS, 8) .attr(GorillaTacticsAbAttr), new Ability(Abilities.NEUTRALIZING_GAS, 8) @@ -5692,7 +5711,7 @@ export function initAbilities() { .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonNeutralizingGas", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) - .partial(), + .partial(), // A bunch of weird interactions with other abilities being suppressed then unsuppressed new Ability(Abilities.PASTEL_VEIL, 8) .attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) @@ -5797,7 +5816,7 @@ export function initAbilities() { new Ability(Abilities.GOOD_AS_GOLD, 9) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.category === MoveCategory.STATUS) .ignorable() - .partial(), + .partial(), // Lots of weird interactions with moves and abilities such as negating status moves that target the field new Ability(Abilities.VESSEL_OF_RUIN, 9) .attr(FieldMultiplyStatAbAttr, Stat.SPATK, 0.75) .attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonVesselOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.SPATK)) })) @@ -5830,7 +5849,7 @@ export function initAbilities() { .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SLICING_MOVE), 1.5), new Ability(Abilities.SUPREME_OVERLORD, 9) .attr(VariableMovePowerBoostAbAttr, (user, target, move) => 1 + 0.1 * Math.min(user.isPlayer() ? user.scene.currentBattle.playerFaints : user.scene.currentBattle.enemyFaints, 5)) - .partial(), + .partial(), // Counter resets every wave new Ability(Abilities.COSTAR, 9) .attr(PostSummonCopyAllyStatsAbAttr), new Ability(Abilities.TOXIC_DEBRIS, 9) @@ -5863,25 +5882,25 @@ export function initAbilities() { .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) - .partial(), + .partial(), // Ogerpon tera interactions new Ability(Abilities.EMBODY_ASPECT_WELLSPRING, 9) .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.SPDEF ], 1, true) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) - .partial(), + .partial(), // Ogerpon tera interactions new Ability(Abilities.EMBODY_ASPECT_HEARTHFLAME, 9) .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.ATK ], 1, true) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) - .partial(), + .partial(), // Ogerpon tera interactions new Ability(Abilities.EMBODY_ASPECT_CORNERSTONE, 9) .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.DEF ], 1, true) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) - .partial(), + .partial(), // Ogerpon tera interactions new Ability(Abilities.TERA_SHIFT, 9) .attr(PostSummonFormChangeAbAttr, p => p.getFormKey() ? 0 : 1) .attr(UncopiableAbilityAbAttr) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 6407e139a71..ed4c2789165 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1,10 +1,10 @@ import { Arena } from "#app/field/arena"; import BattleScene from "#app/battle-scene"; import { Type } from "#app/data/type"; -import * as Utils from "#app/utils"; +import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils"; import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "#app/data/move"; import { getPokemonNameWithAffix } from "#app/messages"; -import Pokemon, { HitResult, PlayerPokemon, PokemonMove, EnemyPokemon } from "#app/field/pokemon"; +import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon"; import { StatusEffect } from "#app/data/status-effect"; import { BattlerIndex } from "#app/battle"; import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; @@ -28,22 +28,15 @@ export enum ArenaTagSide { } export abstract class ArenaTag { - public tagType: ArenaTagType; - public turnCount: integer; - public sourceMove?: Moves; - public sourceId?: integer; - public side: ArenaTagSide; + constructor( + public tagType: ArenaTagType, + public turnCount: number, + public sourceMove?: Moves, + public sourceId?: number, + public side: ArenaTagSide = ArenaTagSide.BOTH + ) {} - - constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) { - this.tagType = tagType; - this.turnCount = turnCount; - this.sourceMove = sourceMove; - this.sourceId = sourceId; - this.side = side; - } - - apply(arena: Arena, args: any[]): boolean { + apply(arena: Arena, simulated: boolean, ...args: unknown[]): boolean { return true; } @@ -66,6 +59,44 @@ export abstract class ArenaTag { ? allMoves[this.sourceMove].name : null; } + + /** + * When given a arena tag or json representing one, load the data for it. + * This is meant to be inherited from by any arena tag with custom attributes + * @param {ArenaTag | any} source An arena tag + */ + loadTag(source : ArenaTag | any) : void { + this.turnCount = source.turnCount; + this.sourceMove = source.sourceMove; + this.sourceId = source.sourceId; + this.side = source.side; + } + + /** + * Helper function that retrieves the source Pokemon + * @param scene medium to retrieve the source Pokemon + * @returns The source {@linkcode Pokemon} or `null` if none is found + */ + public getSourcePokemon(scene: BattleScene): Pokemon | null { + return this.sourceId ? scene.getPokemonById(this.sourceId) : null; + } + + /** + * Helper function that retrieves the Pokemon affected + * @param scene - medium to retrieve the involved Pokemon + * @returns list of PlayerPokemon or EnemyPokemon on the field + */ + public getAffectedPokemon(scene: BattleScene): Pokemon[] { + switch (this.side) { + case ArenaTagSide.PLAYER: + return scene.getPlayerField() ?? []; + case ArenaTagSide.ENEMY: + return scene.getEnemyField() ?? []; + case ArenaTagSide.BOTH: + default: + return scene.getField(true) ?? []; + } + } } /** @@ -73,7 +104,7 @@ export abstract class ArenaTag { * Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist. */ export class MistTag extends ArenaTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side); } @@ -91,10 +122,20 @@ export class MistTag extends ArenaTag { } } - apply(arena: Arena, args: any[]): boolean { - (args[0] as Utils.BooleanHolder).value = true; + /** + * Cancels the lowering of stats + * @param arena the {@linkcode Arena} containing this effect + * @param simulated `true` if the effect should be applied quietly + * @param cancelled a {@linkcode BooleanHolder} whose value is set to `true` + * to flag the stat reduction as cancelled + * @returns `true` if a stat reduction was cancelled; `false` otherwise + */ + override apply(arena: Arena, simulated: boolean, cancelled: BooleanHolder): boolean { + cancelled.value = true; - arena.scene.queueMessage(i18next.t("arenaTag:mistApply")); + if (!simulated) { + arena.scene.queueMessage(i18next.t("arenaTag:mistApply")); + } return true; } @@ -117,7 +158,7 @@ export class WeakenMoveScreenTag extends ArenaTag { * @param side - The side (player or enemy) the tag affects. * @param weakenedCategories - The categories of moves that are weakened by this tag. */ - constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, weakenedCategories: MoveCategory[]) { + constructor(tagType: ArenaTagType, turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide, weakenedCategories: MoveCategory[]) { super(tagType, turnCount, sourceMove, sourceId, side); this.weakenedCategories = weakenedCategories; @@ -126,17 +167,15 @@ export class WeakenMoveScreenTag extends ArenaTag { /** * Applies the weakening effect to the move. * - * @param arena - The arena where the move is applied. - * @param args - The arguments for the move application. - * @param args[0] - The category of the move. - * @param args[1] - A boolean indicating whether it is a double battle. - * @param args[2] - An object of type `Utils.NumberHolder` that holds the damage multiplier - * - * @returns True if the move was weakened, otherwise false. + * @param arena the {@linkcode Arena} where the move is applied. + * @param simulated n/a + * @param moveCategory the attacking move's {@linkcode MoveCategory}. + * @param damageMultiplier A {@linkcode NumberHolder} containing the damage multiplier + * @returns `true` if the attacking move was weakened; `false` otherwise. */ - apply(arena: Arena, args: any[]): boolean { - if (this.weakenedCategories.includes((args[0] as MoveCategory))) { - (args[2] as Utils.NumberHolder).value = (args[1] as boolean) ? 2732 / 4096 : 0.5; + override apply(arena: Arena, simulated: boolean, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean { + if (this.weakenedCategories.includes(moveCategory)) { + damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5; return true; } return false; @@ -148,7 +187,7 @@ export class WeakenMoveScreenTag extends ArenaTag { * Used by {@linkcode Moves.REFLECT} */ class ReflectTag extends WeakenMoveScreenTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.REFLECT, turnCount, Moves.REFLECT, sourceId, side, [ MoveCategory.PHYSICAL ]); } @@ -164,7 +203,7 @@ class ReflectTag extends WeakenMoveScreenTag { * Used by {@linkcode Moves.LIGHT_SCREEN} */ class LightScreenTag extends WeakenMoveScreenTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.LIGHT_SCREEN, turnCount, Moves.LIGHT_SCREEN, sourceId, side, [ MoveCategory.SPECIAL ]); } @@ -180,7 +219,7 @@ class LightScreenTag extends WeakenMoveScreenTag { * Used by {@linkcode Moves.AURORA_VEIL} */ class AuroraVeilTag extends WeakenMoveScreenTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.AURORA_VEIL, turnCount, Moves.AURORA_VEIL, sourceId, side, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ]); } @@ -203,7 +242,7 @@ export class ConditionalProtectTag extends ArenaTag { /** Does this apply to all moves, including those that ignore other forms of protection? */ protected ignoresBypass: boolean; - constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) { + constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) { super(tagType, 1, sourceMove, sourceId, side); this.protectConditionFunc = condition; @@ -218,38 +257,34 @@ export class ConditionalProtectTag extends ArenaTag { onRemove(arena: Arena): void { } /** - * apply(): Checks incoming moves against the condition function + * Checks incoming moves against the condition function * and protects the target if conditions are met - * @param arena The arena containing this tag - * @param args\[0\] (Utils.BooleanHolder) Signals if the move is cancelled - * @param args\[1\] (Pokemon) The Pokemon using the move - * @param args\[2\] (Pokemon) The intended target of the move - * @param args\[3\] (Moves) The parameters to the condition function - * @param args\[4\] (Utils.BooleanHolder) Signals if the applied protection supercedes protection-ignoring effects - * @returns + * @param arena the {@linkcode Arena} containing this tag + * @param simulated `true` if the tag is applied quietly; `false` otherwise. + * @param isProtected a {@linkcode BooleanHolder} used to flag if the move is protected against + * @param attacker the attacking {@linkcode Pokemon} + * @param defender the defending {@linkcode Pokemon} + * @param moveId the {@linkcode Moves | identifier} for the move being used + * @param ignoresProtectBypass a {@linkcode BooleanHolder} used to flag if a protection effect supercedes effects that ignore protection + * @returns `true` if this tag protected against the attack; `false` otherwise */ - apply(arena: Arena, args: any[]): boolean { - const [ cancelled, user, target, moveId, ignoresBypass ] = args; + override apply(arena: Arena, simulated: boolean, isProtected: BooleanHolder, attacker: Pokemon, defender: Pokemon, + moveId: Moves, ignoresProtectBypass: BooleanHolder): boolean { - if (cancelled instanceof Utils.BooleanHolder - && user instanceof Pokemon - && target instanceof Pokemon - && typeof moveId === "number" - && ignoresBypass instanceof Utils.BooleanHolder) { + if ((this.side === ArenaTagSide.PLAYER) === defender.isPlayer() + && this.protectConditionFunc(arena, moveId)) { + if (!isProtected.value) { + isProtected.value = true; + if (!simulated) { + attacker.stopMultiHit(defender); - if ((this.side === ArenaTagSide.PLAYER) === target.isPlayer() - && this.protectConditionFunc(arena, moveId)) { - if (!cancelled.value) { - cancelled.value = true; - user.stopMultiHit(target); - - new CommonBattleAnim(CommonAnim.PROTECT, target).play(arena.scene); - arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + new CommonBattleAnim(CommonAnim.PROTECT, defender).play(arena.scene); + arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender) })); } - - ignoresBypass.value = ignoresBypass.value || this.ignoresBypass; - return true; } + + ignoresProtectBypass.value = ignoresProtectBypass.value || this.ignoresBypass; + return true; } return false; } @@ -265,7 +300,7 @@ export class ConditionalProtectTag extends ArenaTag { */ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => { const move = allMoves[moveId]; - const priority = new Utils.IntegerHolder(move.priority); + const priority = new NumberHolder(move.priority); const effectPhase = arena.scene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase) { @@ -281,7 +316,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => { * Condition: The incoming move has increased priority. */ class QuickGuardTag extends ConditionalProtectTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.QUICK_GUARD, Moves.QUICK_GUARD, sourceId, side, QuickGuardConditionFunc); } } @@ -297,11 +332,11 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean = const move = allMoves[moveId]; switch (move.moveTarget) { - case MoveTarget.ALL_ENEMIES: - case MoveTarget.ALL_NEAR_ENEMIES: - case MoveTarget.ALL_OTHERS: - case MoveTarget.ALL_NEAR_OTHERS: - return true; + case MoveTarget.ALL_ENEMIES: + case MoveTarget.ALL_NEAR_ENEMIES: + case MoveTarget.ALL_OTHERS: + case MoveTarget.ALL_NEAR_OTHERS: + return true; } return false; }; @@ -312,7 +347,7 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean = * can be an ally or enemy. */ class WideGuardTag extends ConditionalProtectTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.WIDE_GUARD, Moves.WIDE_GUARD, sourceId, side, WideGuardConditionFunc); } } @@ -334,7 +369,7 @@ const MatBlockConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean => * Condition: The incoming move is a Physical or Special attack move. */ class MatBlockTag extends ConditionalProtectTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.MAT_BLOCK, Moves.MAT_BLOCK, sourceId, side, MatBlockConditionFunc); } @@ -372,7 +407,7 @@ const CraftyShieldConditionFunc: ProtectConditionFunc = (arena, moveId) => { * not target all Pokemon or sides of the field. */ class CraftyShieldTag extends ConditionalProtectTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.CRAFTY_SHIELD, Moves.CRAFTY_SHIELD, sourceId, side, CraftyShieldConditionFunc, true); } } @@ -384,12 +419,12 @@ class CraftyShieldTag extends ConditionalProtectTag { export class NoCritTag extends ArenaTag { /** * Constructor method for the NoCritTag class - * @param turnCount `integer` the number of turns this effect lasts + * @param turnCount `number` the number of turns this effect lasts * @param sourceMove {@linkcode Moves} the move that created this effect - * @param sourceId `integer` the ID of the {@linkcode Pokemon} that created this effect + * @param sourceId `number` the ID of the {@linkcode Pokemon} that created this effect * @param side {@linkcode ArenaTagSide} the side to which this effect belongs */ - constructor(turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.NO_CRIT, turnCount, sourceMove, sourceId, side); } @@ -419,7 +454,7 @@ class WishTag extends ArenaTag { private triggerMessage: string; private healHp: number; - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.WISH, turnCount, Moves.WISH, sourceId, side); } @@ -429,7 +464,7 @@ class WishTag extends ArenaTag { if (user) { this.battlerIndex = user.getBattlerIndex(); this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) }); - this.healHp = Utils.toDmgValue(user.getMaxHp() / 2); + this.healHp = toDmgValue(user.getMaxHp() / 2); } else { console.warn("Failed to get source for WishTag onAdd"); } @@ -460,18 +495,25 @@ export class WeakenMoveTypeTag extends ArenaTag { * @param sourceMove - The move that created the tag. * @param sourceId - The ID of the source of the tag. */ - constructor(tagType: ArenaTagType, turnCount: integer, type: Type, sourceMove: Moves, sourceId: integer) { + constructor(tagType: ArenaTagType, turnCount: number, type: Type, sourceMove: Moves, sourceId: number) { super(tagType, turnCount, sourceMove, sourceId); this.weakenedType = type; } - apply(arena: Arena, args: any[]): boolean { - if ((args[0] as Type) === this.weakenedType) { - (args[1] as Utils.NumberHolder).value *= 0.33; + /** + * Reduces an attack's power by 0.33x if it matches this tag's weakened type. + * @param arena n/a + * @param simulated n/a + * @param type the attack's {@linkcode Type} + * @param power a {@linkcode NumberHolder} containing the attack's power + * @returns `true` if the attack's power was reduced; `false` otherwise. + */ + override apply(arena: Arena, simulated: boolean, type: Type, power: NumberHolder): boolean { + if (type === this.weakenedType) { + power.value *= 0.33; return true; } - return false; } } @@ -481,7 +523,7 @@ export class WeakenMoveTypeTag extends ArenaTag { * Weakens Electric type moves for a set amount of turns, usually 5. */ class MudSportTag extends WeakenMoveTypeTag { - constructor(turnCount: integer, sourceId: integer) { + constructor(turnCount: number, sourceId: number) { super(ArenaTagType.MUD_SPORT, turnCount, Type.ELECTRIC, Moves.MUD_SPORT, sourceId); } @@ -499,7 +541,7 @@ class MudSportTag extends WeakenMoveTypeTag { * Weakens Fire type moves for a set amount of turns, usually 5. */ class WaterSportTag extends WeakenMoveTypeTag { - constructor(turnCount: integer, sourceId: integer) { + constructor(turnCount: number, sourceId: number) { super(ArenaTagType.WATER_SPORT, turnCount, Type.FIRE, Moves.WATER_SPORT, sourceId); } @@ -532,13 +574,12 @@ export class IonDelugeTag extends ArenaTag { /** * Converts Normal-type moves to Electric type * @param arena n/a - * @param args - * - `[0]` {@linkcode Utils.NumberHolder} A container with a move's {@linkcode Type} + * @param simulated n/a + * @param moveType a {@linkcode NumberHolder} containing a move's {@linkcode Type} * @returns `true` if the given move type changed; `false` otherwise. */ - apply(arena: Arena, args: any[]): boolean { - const moveType = args[0]; - if (moveType instanceof Utils.NumberHolder && moveType.value === Type.NORMAL) { + override apply(arena: Arena, simulated: boolean, moveType: NumberHolder): boolean { + if (moveType.value === Type.NORMAL) { moveType.value = Type.ELECTRIC; return true; } @@ -550,8 +591,8 @@ export class IonDelugeTag extends ArenaTag { * Abstract class to implement arena traps. */ export class ArenaTrapTag extends ArenaTag { - public layers: integer; - public maxLayers: integer; + public layers: number; + public maxLayers: number; /** * Creates a new instance of the ArenaTrapTag class. @@ -562,7 +603,7 @@ export class ArenaTrapTag extends ArenaTag { * @param side - The side (player or enemy) the tag affects. * @param maxLayers - The maximum amount of layers this tag can have. */ - constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, maxLayers: integer) { + constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, maxLayers: number) { super(tagType, 0, sourceMove, sourceId, side); this.layers = 1; @@ -577,22 +618,34 @@ export class ArenaTrapTag extends ArenaTag { } } - apply(arena: Arena, args: any[]): boolean { - const pokemon = args[0] as Pokemon; - if (this.sourceId === pokemon.id || (this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) { + /** + * Activates the hazard effect onto a Pokemon when it enters the field + * @param arena the {@linkcode Arena} containing this tag + * @param simulated if `true`, only checks if the hazard would activate. + * @param pokemon the {@linkcode Pokemon} triggering this hazard + * @returns `true` if this hazard affects the given Pokemon; `false` otherwise. + */ + override apply(arena: Arena, simulated: boolean, pokemon: Pokemon): boolean { + if ((this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) { return false; } - return this.activateTrap(pokemon); + return this.activateTrap(pokemon, simulated); } - activateTrap(pokemon: Pokemon): boolean { + activateTrap(pokemon: Pokemon, simulated: boolean): boolean { return false; } getMatchupScoreMultiplier(pokemon: Pokemon): number { return pokemon.isGrounded() ? 1 : Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2); } + + loadTag(source: any): void { + super.loadTag(source); + this.layers = source.layers; + this.maxLayers = source.maxLayers; + } } /** @@ -601,7 +654,7 @@ export class ArenaTrapTag extends ArenaTag { * in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap. */ class SpikesTag extends ArenaTrapTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3); } @@ -614,14 +667,18 @@ class SpikesTag extends ArenaTrapTag { } } - activateTrap(pokemon: Pokemon): boolean { + override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { if (pokemon.isGrounded()) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + if (simulated) { + return !cancelled.value; + } + if (!cancelled.value) { const damageHpRatio = 1 / (10 - 2 * this.layers); - const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio); + const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); pokemon.scene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.damageAndUpdate(damage, HitResult.OTHER); @@ -645,7 +702,7 @@ class SpikesTag extends ArenaTrapTag { class ToxicSpikesTag extends ArenaTrapTag { private neutralized: boolean; - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, side, 2); this.neutralized = false; } @@ -665,8 +722,11 @@ class ToxicSpikesTag extends ArenaTrapTag { } } - activateTrap(pokemon: Pokemon): boolean { + override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { if (pokemon.isGrounded()) { + if (simulated) { + return true; + } if (pokemon.isOfType(Type.POISON)) { this.neutralized = true; if (pokemon.scene.arena.removeTag(this.tagType)) { @@ -703,7 +763,7 @@ class ToxicSpikesTag extends ArenaTrapTag { class DelayedAttackTag extends ArenaTag { public targetIndex: BattlerIndex; - constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: integer, targetIndex: BattlerIndex) { + constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: number, targetIndex: BattlerIndex) { super(tagType, 3, sourceMove, sourceId); this.targetIndex = targetIndex; @@ -728,7 +788,7 @@ class DelayedAttackTag extends ArenaTag { * who is summoned into the trap, based on the Rock type's type effectiveness. */ class StealthRockTag extends ArenaTrapTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1); } @@ -747,31 +807,31 @@ class StealthRockTag extends ArenaTrapTag { let damageHpRatio: number = 0; switch (effectiveness) { - case 0: - damageHpRatio = 0; - break; - case 0.25: - damageHpRatio = 0.03125; - break; - case 0.5: - damageHpRatio = 0.0625; - break; - case 1: - damageHpRatio = 0.125; - break; - case 2: - damageHpRatio = 0.25; - break; - case 4: - damageHpRatio = 0.5; - break; + case 0: + damageHpRatio = 0; + break; + case 0.25: + damageHpRatio = 0.03125; + break; + case 0.5: + damageHpRatio = 0.0625; + break; + case 1: + damageHpRatio = 0.125; + break; + case 2: + damageHpRatio = 0.25; + break; + case 4: + damageHpRatio = 0.5; + break; } return damageHpRatio; } - activateTrap(pokemon: Pokemon): boolean { - const cancelled = new Utils.BooleanHolder(false); + override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (cancelled.value) { @@ -781,12 +841,16 @@ class StealthRockTag extends ArenaTrapTag { const damageHpRatio = this.getDamageHpRatio(pokemon); if (damageHpRatio) { - const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio); + if (simulated) { + return true; + } + const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); pokemon.scene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.damageAndUpdate(damage, HitResult.OTHER); if (pokemon.turnData) { pokemon.turnData.damageTaken += damage; } + return true; } return false; @@ -804,7 +868,7 @@ class StealthRockTag extends ArenaTrapTag { * to any Pokémon who is summoned into this trap. */ class StickyWebTag extends ArenaTrapTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.STICKY_WEB, Moves.STICKY_WEB, sourceId, side, 1); } @@ -816,14 +880,20 @@ class StickyWebTag extends ArenaTrapTag { } } - activateTrap(pokemon: Pokemon): boolean { + override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { if (pokemon.isGrounded()) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); + + if (simulated) { + return !cancelled.value; + } + if (!cancelled.value) { pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() })); - const stages = new Utils.NumberHolder(-1); + const stages = new NumberHolder(-1); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value)); + return true; } } @@ -838,12 +908,19 @@ class StickyWebTag extends ArenaTrapTag { * also reversing the turn order for all Pokémon on the field as well. */ export class TrickRoomTag extends ArenaTag { - constructor(turnCount: integer, sourceId: integer) { + constructor(turnCount: number, sourceId: number) { super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId); } - apply(arena: Arena, args: any[]): boolean { - const speedReversed = args[0] as Utils.BooleanHolder; + /** + * Reverses Speed-based turn order for all Pokemon on the field + * @param arena n/a + * @param simulated n/a + * @param speedReversed a {@linkcode BooleanHolder} used to flag if Speed-based + * turn order should be reversed. + * @returns `true` if turn order is successfully reversed; `false` otherwise + */ + override apply(arena: Arena, simulated: boolean, speedReversed: BooleanHolder): boolean { speedReversed.value = !speedReversed.value; return true; } @@ -866,7 +943,7 @@ export class TrickRoomTag extends ArenaTag { * {@linkcode Abilities.LEVITATE} for the duration of the arena tag, usually 5 turns. */ export class GravityTag extends ArenaTag { - constructor(turnCount: integer) { + constructor(turnCount: number) { super(ArenaTagType.GRAVITY, turnCount, Moves.GRAVITY); } @@ -874,7 +951,8 @@ export class GravityTag extends ArenaTag { arena.scene.queueMessage(i18next.t("arenaTag:gravityOnAdd")); arena.scene.getField(true).forEach((pokemon) => { if (pokemon !== null) { - pokemon.removeTag(BattlerTagType.MAGNET_RISEN); + pokemon.removeTag(BattlerTagType.FLOATING); + pokemon.removeTag(BattlerTagType.TELEKINESIS); } }); } @@ -890,7 +968,7 @@ export class GravityTag extends ArenaTag { * Applies this arena tag for 4 turns (including the turn the move was used). */ class TailwindTag extends ArenaTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.TAILWIND, turnCount, Moves.TAILWIND, sourceId, side); } @@ -928,7 +1006,7 @@ class TailwindTag extends ArenaTag { * Doubles the prize money from trainers and money moves like {@linkcode Moves.PAY_DAY} and {@linkcode Moves.MAKE_IT_RAIN}. */ class HappyHourTag extends ArenaTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.HAPPY_HOUR, turnCount, Moves.HAPPY_HOUR, sourceId, side); } @@ -942,7 +1020,7 @@ class HappyHourTag extends ArenaTag { } class SafeguardTag extends ArenaTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.SAFEGUARD, turnCount, Moves.SAFEGUARD, sourceId, side); } @@ -955,42 +1033,35 @@ class SafeguardTag extends ArenaTag { } } +class NoneTag extends ArenaTag { + constructor() { + super(ArenaTagType.NONE, 0); + } +} /** * This arena tag facilitates the application of the move Imprison * Imprison remains in effect as long as the source Pokemon is active and present on the field. * Imprison will apply to any opposing Pokemon that switch onto the field as well. */ class ImprisonTag extends ArenaTrapTag { - private source: Pokemon; - constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1); } - /** - * Helper function that retrieves the Pokemon affected - * @param {BattleScene} scene medium to retrieve the involved Pokemon - * @returns list of PlayerPokemon or EnemyPokemon on the field - */ - private retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] { - if (!this.source.isPlayer()) { - return scene.getPlayerField() ?? []; - } - return scene.getEnemyField() ?? []; - } - /** * This function applies the effects of Imprison to the opposing Pokemon already present on the field. * @param arena */ override onAdd({ scene }: Arena) { - this.source = scene.getPokemonById(this.sourceId!)!; - if (this.source) { - const party = this.retrieveField(scene); - party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { - p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); + const source = this.getSourcePokemon(scene); + if (source) { + const party = this.getAffectedPokemon(scene); + party?.forEach((p: Pokemon ) => { + if (p.isAllowedInBattle()) { + p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); + } }); - scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(this.source) })); + scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); } } @@ -999,8 +1070,9 @@ class ImprisonTag extends ArenaTrapTag { * @param _arena * @returns `true` if the source of the tag is still active on the field | `false` if not */ - override lapse(_arena: Arena): boolean { - return this.source.isActive(true); + override lapse({ scene }: Arena): boolean { + const source = this.getSourcePokemon(scene); + return source ? source.isActive(true) : false; } /** @@ -1009,7 +1081,8 @@ class ImprisonTag extends ArenaTrapTag { * @returns `true` */ override activateTrap(pokemon: Pokemon): boolean { - if (this.source.isActive(true)) { + const source = this.getSourcePokemon(pokemon.scene); + if (source && source.isActive(true) && pokemon.isAllowedInBattle()) { pokemon.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); } return true; @@ -1020,8 +1093,8 @@ class ImprisonTag extends ArenaTrapTag { * @param arena */ override onRemove({ scene }: Arena): void { - const party = this.retrieveField(scene); - party?.forEach((p: PlayerPokemon | EnemyPokemon) => { + const party = this.getAffectedPokemon(scene); + party?.forEach((p: Pokemon) => { p.removeTag(BattlerTagType.IMPRISON); }); } @@ -1054,7 +1127,7 @@ class FireGrassPledgeTag extends ArenaTag { pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // TODO: Replace this with a proper animation pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM)); - pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8)); + pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8)); }); return super.lapse(arena); @@ -1078,8 +1151,15 @@ class WaterFirePledgeTag extends ArenaTag { arena.scene.queueMessage(i18next.t(`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } - override apply(arena: Arena, args: any[]): boolean { - const moveChance = args[0] as Utils.NumberHolder; + /** + * Doubles the chance for the given move's secondary effect(s) to trigger + * @param arena the {@linkcode Arena} containing this tag + * @param simulated n/a + * @param moveChance a {@linkcode NumberHolder} containing + * the move's current effect chance + * @returns `true` if the move's effect chance was doubled (currently always `true`) + */ + override apply(arena: Arena, simulated: boolean, moveChance: NumberHolder): boolean { moveChance.value *= 2; return true; } @@ -1102,64 +1182,78 @@ class GrassWaterPledgeTag extends ArenaTag { } } -export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null { +// TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter +export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null { switch (tagType) { - case ArenaTagType.MIST: - return new MistTag(turnCount, sourceId, side); - case ArenaTagType.QUICK_GUARD: - return new QuickGuardTag(sourceId, side); - case ArenaTagType.WIDE_GUARD: - return new WideGuardTag(sourceId, side); - case ArenaTagType.MAT_BLOCK: - return new MatBlockTag(sourceId, side); - case ArenaTagType.CRAFTY_SHIELD: - return new CraftyShieldTag(sourceId, side); - case ArenaTagType.NO_CRIT: - return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct? - case ArenaTagType.MUD_SPORT: - return new MudSportTag(turnCount, sourceId); - case ArenaTagType.WATER_SPORT: - return new WaterSportTag(turnCount, sourceId); - case ArenaTagType.ION_DELUGE: - return new IonDelugeTag(sourceMove); - case ArenaTagType.SPIKES: - return new SpikesTag(sourceId, side); - case ArenaTagType.TOXIC_SPIKES: - return new ToxicSpikesTag(sourceId, side); - case ArenaTagType.FUTURE_SIGHT: - case ArenaTagType.DOOM_DESIRE: - return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang - case ArenaTagType.WISH: - return new WishTag(turnCount, sourceId, side); - case ArenaTagType.STEALTH_ROCK: - return new StealthRockTag(sourceId, side); - case ArenaTagType.STICKY_WEB: - return new StickyWebTag(sourceId, side); - case ArenaTagType.TRICK_ROOM: - return new TrickRoomTag(turnCount, sourceId); - case ArenaTagType.GRAVITY: - return new GravityTag(turnCount); - case ArenaTagType.REFLECT: - return new ReflectTag(turnCount, sourceId, side); - case ArenaTagType.LIGHT_SCREEN: - return new LightScreenTag(turnCount, sourceId, side); - case ArenaTagType.AURORA_VEIL: - return new AuroraVeilTag(turnCount, sourceId, side); - case ArenaTagType.TAILWIND: - return new TailwindTag(turnCount, sourceId, side); - case ArenaTagType.HAPPY_HOUR: - return new HappyHourTag(turnCount, sourceId, side); - case ArenaTagType.SAFEGUARD: - return new SafeguardTag(turnCount, sourceId, side); - case ArenaTagType.IMPRISON: - return new ImprisonTag(sourceId, side); - case ArenaTagType.FIRE_GRASS_PLEDGE: - return new FireGrassPledgeTag(sourceId, side); - case ArenaTagType.WATER_FIRE_PLEDGE: - return new WaterFirePledgeTag(sourceId, side); - case ArenaTagType.GRASS_WATER_PLEDGE: - return new GrassWaterPledgeTag(sourceId, side); - default: - return null; + case ArenaTagType.MIST: + return new MistTag(turnCount, sourceId, side); + case ArenaTagType.QUICK_GUARD: + return new QuickGuardTag(sourceId, side); + case ArenaTagType.WIDE_GUARD: + return new WideGuardTag(sourceId, side); + case ArenaTagType.MAT_BLOCK: + return new MatBlockTag(sourceId, side); + case ArenaTagType.CRAFTY_SHIELD: + return new CraftyShieldTag(sourceId, side); + case ArenaTagType.NO_CRIT: + return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct? + case ArenaTagType.MUD_SPORT: + return new MudSportTag(turnCount, sourceId); + case ArenaTagType.WATER_SPORT: + return new WaterSportTag(turnCount, sourceId); + case ArenaTagType.ION_DELUGE: + return new IonDelugeTag(sourceMove); + case ArenaTagType.SPIKES: + return new SpikesTag(sourceId, side); + case ArenaTagType.TOXIC_SPIKES: + return new ToxicSpikesTag(sourceId, side); + case ArenaTagType.FUTURE_SIGHT: + case ArenaTagType.DOOM_DESIRE: + return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang + case ArenaTagType.WISH: + return new WishTag(turnCount, sourceId, side); + case ArenaTagType.STEALTH_ROCK: + return new StealthRockTag(sourceId, side); + case ArenaTagType.STICKY_WEB: + return new StickyWebTag(sourceId, side); + case ArenaTagType.TRICK_ROOM: + return new TrickRoomTag(turnCount, sourceId); + case ArenaTagType.GRAVITY: + return new GravityTag(turnCount); + case ArenaTagType.REFLECT: + return new ReflectTag(turnCount, sourceId, side); + case ArenaTagType.LIGHT_SCREEN: + return new LightScreenTag(turnCount, sourceId, side); + case ArenaTagType.AURORA_VEIL: + return new AuroraVeilTag(turnCount, sourceId, side); + case ArenaTagType.TAILWIND: + return new TailwindTag(turnCount, sourceId, side); + case ArenaTagType.HAPPY_HOUR: + return new HappyHourTag(turnCount, sourceId, side); + case ArenaTagType.SAFEGUARD: + return new SafeguardTag(turnCount, sourceId, side); + case ArenaTagType.IMPRISON: + return new ImprisonTag(sourceId, side); + case ArenaTagType.FIRE_GRASS_PLEDGE: + return new FireGrassPledgeTag(sourceId, side); + case ArenaTagType.WATER_FIRE_PLEDGE: + return new WaterFirePledgeTag(sourceId, side); + case ArenaTagType.GRASS_WATER_PLEDGE: + return new GrassWaterPledgeTag(sourceId, side); + default: + return null; } } + +/** + * When given a battler tag or json representing one, creates an actual ArenaTag object with the same data. + * @param {ArenaTag | any} source An arena tag + * @return {ArenaTag} The valid arena tag + */ +export function loadArenaTag(source: ArenaTag | any): ArenaTag { + const tag = getArenaTag(source.tagType, source.turnCount, source.sourceMove, source.sourceId, source.targetIndex, source.side) + ?? new NoneTag(); + tag.loadTag(source); + return tag; +} + diff --git a/src/data/balance/biomes.ts b/src/data/balance/biomes.ts index 7ef83b654db..2ce693c360b 100644 --- a/src/data/balance/biomes.ts +++ b/src/data/balance/biomes.ts @@ -13,14 +13,14 @@ export function getBiomeName(biome: Biome | -1) { return i18next.t("biome:unknownLocation"); } switch (biome) { - case Biome.GRASS: - return i18next.t("biome:GRASS"); - case Biome.RUINS: - return i18next.t("biome:RUINS"); - case Biome.END: - return i18next.t("biome:END"); - default: - return i18next.t(`biome:${Biome[biome].toUpperCase()}`); + case Biome.GRASS: + return i18next.t("biome:GRASS"); + case Biome.RUINS: + return i18next.t("biome:RUINS"); + case Biome.END: + return i18next.t("biome:END"); + default: + return i18next.t(`biome:${Biome[biome].toUpperCase()}`); } } diff --git a/src/data/balance/species-egg-tiers.ts b/src/data/balance/species-egg-tiers.ts new file mode 100644 index 00000000000..cd266dfcf54 --- /dev/null +++ b/src/data/balance/species-egg-tiers.ts @@ -0,0 +1,603 @@ +import { Species } from "#enums/species"; +import { EggTier } from "#enums/egg-type"; + +/** + * Map of all starters and their respective {@linkcode EggTier}, which determines the type of egg the starter hatches from. + */ +export const speciesEggTiers = { + [Species.BULBASAUR]: EggTier.COMMON, + [Species.CHARMANDER]: EggTier.COMMON, + [Species.SQUIRTLE]: EggTier.COMMON, + [Species.CATERPIE]: EggTier.COMMON, + [Species.WEEDLE]: EggTier.COMMON, + [Species.PIDGEY]: EggTier.COMMON, + [Species.RATTATA]: EggTier.COMMON, + [Species.SPEAROW]: EggTier.COMMON, + [Species.EKANS]: EggTier.COMMON, + [Species.PIKACHU]: EggTier.COMMON, + [Species.SANDSHREW]: EggTier.COMMON, + [Species.NIDORAN_F]: EggTier.COMMON, + [Species.NIDORAN_M]: EggTier.COMMON, + [Species.CLEFAIRY]: EggTier.COMMON, + [Species.VULPIX]: EggTier.COMMON, + [Species.JIGGLYPUFF]: EggTier.COMMON, + [Species.ZUBAT]: EggTier.COMMON, + [Species.ODDISH]: EggTier.COMMON, + [Species.PARAS]: EggTier.COMMON, + [Species.VENONAT]: EggTier.COMMON, + [Species.DIGLETT]: EggTier.COMMON, + [Species.MEOWTH]: EggTier.COMMON, + [Species.PSYDUCK]: EggTier.COMMON, + [Species.MANKEY]: EggTier.RARE, + [Species.GROWLITHE]: EggTier.RARE, + [Species.POLIWAG]: EggTier.COMMON, + [Species.ABRA]: EggTier.RARE, + [Species.MACHOP]: EggTier.COMMON, + [Species.BELLSPROUT]: EggTier.COMMON, + [Species.TENTACOOL]: EggTier.COMMON, + [Species.GEODUDE]: EggTier.COMMON, + [Species.PONYTA]: EggTier.COMMON, + [Species.SLOWPOKE]: EggTier.COMMON, + [Species.MAGNEMITE]: EggTier.RARE, + [Species.FARFETCHD]: EggTier.COMMON, + [Species.DODUO]: EggTier.COMMON, + [Species.SEEL]: EggTier.COMMON, + [Species.GRIMER]: EggTier.COMMON, + [Species.SHELLDER]: EggTier.RARE, + [Species.GASTLY]: EggTier.RARE, + [Species.ONIX]: EggTier.COMMON, + [Species.DROWZEE]: EggTier.COMMON, + [Species.KRABBY]: EggTier.COMMON, + [Species.VOLTORB]: EggTier.COMMON, + [Species.EXEGGCUTE]: EggTier.COMMON, + [Species.CUBONE]: EggTier.COMMON, + [Species.HITMONLEE]: EggTier.RARE, + [Species.HITMONCHAN]: EggTier.RARE, + [Species.LICKITUNG]: EggTier.COMMON, + [Species.KOFFING]: EggTier.COMMON, + [Species.RHYHORN]: EggTier.COMMON, + [Species.CHANSEY]: EggTier.COMMON, + [Species.TANGELA]: EggTier.COMMON, + [Species.KANGASKHAN]: EggTier.RARE, + [Species.HORSEA]: EggTier.COMMON, + [Species.GOLDEEN]: EggTier.COMMON, + [Species.STARYU]: EggTier.COMMON, + [Species.MR_MIME]: EggTier.COMMON, + [Species.SCYTHER]: EggTier.RARE, + [Species.JYNX]: EggTier.RARE, + [Species.ELECTABUZZ]: EggTier.RARE, + [Species.MAGMAR]: EggTier.RARE, + [Species.PINSIR]: EggTier.RARE, + [Species.TAUROS]: EggTier.RARE, + [Species.MAGIKARP]: EggTier.RARE, + [Species.LAPRAS]: EggTier.RARE, + [Species.DITTO]: EggTier.COMMON, + [Species.EEVEE]: EggTier.COMMON, + [Species.PORYGON]: EggTier.RARE, + [Species.OMANYTE]: EggTier.COMMON, + [Species.KABUTO]: EggTier.COMMON, + [Species.AERODACTYL]: EggTier.RARE, + [Species.SNORLAX]: EggTier.RARE, + [Species.ARTICUNO]: EggTier.EPIC, + [Species.ZAPDOS]: EggTier.EPIC, + [Species.MOLTRES]: EggTier.EPIC, + [Species.DRATINI]: EggTier.RARE, + [Species.MEWTWO]: EggTier.LEGENDARY, + [Species.MEW]: EggTier.EPIC, + + [Species.CHIKORITA]: EggTier.COMMON, + [Species.CYNDAQUIL]: EggTier.COMMON, + [Species.TOTODILE]: EggTier.COMMON, + [Species.SENTRET]: EggTier.COMMON, + [Species.HOOTHOOT]: EggTier.COMMON, + [Species.LEDYBA]: EggTier.COMMON, + [Species.SPINARAK]: EggTier.COMMON, + [Species.CHINCHOU]: EggTier.COMMON, + [Species.PICHU]: EggTier.COMMON, + [Species.CLEFFA]: EggTier.COMMON, + [Species.IGGLYBUFF]: EggTier.COMMON, + [Species.TOGEPI]: EggTier.COMMON, + [Species.NATU]: EggTier.COMMON, + [Species.MAREEP]: EggTier.COMMON, + [Species.MARILL]: EggTier.RARE, + [Species.SUDOWOODO]: EggTier.COMMON, + [Species.HOPPIP]: EggTier.COMMON, + [Species.AIPOM]: EggTier.COMMON, + [Species.SUNKERN]: EggTier.COMMON, + [Species.YANMA]: EggTier.COMMON, + [Species.WOOPER]: EggTier.COMMON, + [Species.MURKROW]: EggTier.COMMON, + [Species.MISDREAVUS]: EggTier.COMMON, + [Species.UNOWN]: EggTier.COMMON, + [Species.WOBBUFFET]: EggTier.COMMON, + [Species.GIRAFARIG]: EggTier.COMMON, + [Species.PINECO]: EggTier.COMMON, + [Species.DUNSPARCE]: EggTier.COMMON, + [Species.GLIGAR]: EggTier.COMMON, + [Species.SNUBBULL]: EggTier.COMMON, + [Species.QWILFISH]: EggTier.COMMON, + [Species.SHUCKLE]: EggTier.COMMON, + [Species.HERACROSS]: EggTier.RARE, + [Species.SNEASEL]: EggTier.RARE, + [Species.TEDDIURSA]: EggTier.RARE, + [Species.SLUGMA]: EggTier.COMMON, + [Species.SWINUB]: EggTier.COMMON, + [Species.CORSOLA]: EggTier.COMMON, + [Species.REMORAID]: EggTier.COMMON, + [Species.DELIBIRD]: EggTier.COMMON, + [Species.MANTINE]: EggTier.COMMON, + [Species.SKARMORY]: EggTier.RARE, + [Species.HOUNDOUR]: EggTier.COMMON, + [Species.PHANPY]: EggTier.COMMON, + [Species.STANTLER]: EggTier.COMMON, + [Species.SMEARGLE]: EggTier.COMMON, + [Species.TYROGUE]: EggTier.COMMON, + [Species.SMOOCHUM]: EggTier.COMMON, + [Species.ELEKID]: EggTier.COMMON, + [Species.MAGBY]: EggTier.COMMON, + [Species.MILTANK]: EggTier.RARE, + [Species.RAIKOU]: EggTier.EPIC, + [Species.ENTEI]: EggTier.EPIC, + [Species.SUICUNE]: EggTier.EPIC, + [Species.LARVITAR]: EggTier.RARE, + [Species.LUGIA]: EggTier.LEGENDARY, + [Species.HO_OH]: EggTier.LEGENDARY, + [Species.CELEBI]: EggTier.EPIC, + + [Species.TREECKO]: EggTier.COMMON, + [Species.TORCHIC]: EggTier.RARE, + [Species.MUDKIP]: EggTier.COMMON, + [Species.POOCHYENA]: EggTier.COMMON, + [Species.ZIGZAGOON]: EggTier.COMMON, + [Species.WURMPLE]: EggTier.COMMON, + [Species.LOTAD]: EggTier.COMMON, + [Species.SEEDOT]: EggTier.COMMON, + [Species.TAILLOW]: EggTier.COMMON, + [Species.WINGULL]: EggTier.COMMON, + [Species.RALTS]: EggTier.COMMON, + [Species.SURSKIT]: EggTier.COMMON, + [Species.SHROOMISH]: EggTier.COMMON, + [Species.SLAKOTH]: EggTier.RARE, + [Species.NINCADA]: EggTier.RARE, + [Species.WHISMUR]: EggTier.COMMON, + [Species.MAKUHITA]: EggTier.COMMON, + [Species.AZURILL]: EggTier.RARE, + [Species.NOSEPASS]: EggTier.COMMON, + [Species.SKITTY]: EggTier.COMMON, + [Species.SABLEYE]: EggTier.COMMON, + [Species.MAWILE]: EggTier.COMMON, + [Species.ARON]: EggTier.COMMON, + [Species.MEDITITE]: EggTier.COMMON, + [Species.ELECTRIKE]: EggTier.COMMON, + [Species.PLUSLE]: EggTier.COMMON, + [Species.MINUN]: EggTier.COMMON, + [Species.VOLBEAT]: EggTier.COMMON, + [Species.ILLUMISE]: EggTier.COMMON, + [Species.ROSELIA]: EggTier.COMMON, + [Species.GULPIN]: EggTier.COMMON, + [Species.CARVANHA]: EggTier.COMMON, + [Species.WAILMER]: EggTier.COMMON, + [Species.NUMEL]: EggTier.COMMON, + [Species.TORKOAL]: EggTier.COMMON, + [Species.SPOINK]: EggTier.COMMON, + [Species.SPINDA]: EggTier.COMMON, + [Species.TRAPINCH]: EggTier.COMMON, + [Species.CACNEA]: EggTier.COMMON, + [Species.SWABLU]: EggTier.COMMON, + [Species.ZANGOOSE]: EggTier.RARE, + [Species.SEVIPER]: EggTier.COMMON, + [Species.LUNATONE]: EggTier.COMMON, + [Species.SOLROCK]: EggTier.COMMON, + [Species.BARBOACH]: EggTier.COMMON, + [Species.CORPHISH]: EggTier.COMMON, + [Species.BALTOY]: EggTier.COMMON, + [Species.LILEEP]: EggTier.COMMON, + [Species.ANORITH]: EggTier.COMMON, + [Species.FEEBAS]: EggTier.RARE, + [Species.CASTFORM]: EggTier.COMMON, + [Species.KECLEON]: EggTier.COMMON, + [Species.SHUPPET]: EggTier.COMMON, + [Species.DUSKULL]: EggTier.COMMON, + [Species.TROPIUS]: EggTier.COMMON, + [Species.CHIMECHO]: EggTier.COMMON, + [Species.ABSOL]: EggTier.RARE, + [Species.WYNAUT]: EggTier.COMMON, + [Species.SNORUNT]: EggTier.COMMON, + [Species.SPHEAL]: EggTier.COMMON, + [Species.CLAMPERL]: EggTier.COMMON, + [Species.RELICANTH]: EggTier.COMMON, + [Species.LUVDISC]: EggTier.COMMON, + [Species.BAGON]: EggTier.RARE, + [Species.BELDUM]: EggTier.RARE, + [Species.REGIROCK]: EggTier.EPIC, + [Species.REGICE]: EggTier.EPIC, + [Species.REGISTEEL]: EggTier.EPIC, + [Species.LATIAS]: EggTier.EPIC, + [Species.LATIOS]: EggTier.EPIC, + [Species.KYOGRE]: EggTier.LEGENDARY, + [Species.GROUDON]: EggTier.LEGENDARY, + [Species.RAYQUAZA]: EggTier.LEGENDARY, + [Species.JIRACHI]: EggTier.EPIC, + [Species.DEOXYS]: EggTier.EPIC, + + [Species.TURTWIG]: EggTier.COMMON, + [Species.CHIMCHAR]: EggTier.COMMON, + [Species.PIPLUP]: EggTier.COMMON, + [Species.STARLY]: EggTier.COMMON, + [Species.BIDOOF]: EggTier.COMMON, + [Species.KRICKETOT]: EggTier.COMMON, + [Species.SHINX]: EggTier.COMMON, + [Species.BUDEW]: EggTier.COMMON, + [Species.CRANIDOS]: EggTier.COMMON, + [Species.SHIELDON]: EggTier.COMMON, + [Species.BURMY]: EggTier.COMMON, + [Species.COMBEE]: EggTier.COMMON, + [Species.PACHIRISU]: EggTier.COMMON, + [Species.BUIZEL]: EggTier.COMMON, + [Species.CHERUBI]: EggTier.COMMON, + [Species.SHELLOS]: EggTier.COMMON, + [Species.DRIFLOON]: EggTier.COMMON, + [Species.BUNEARY]: EggTier.COMMON, + [Species.GLAMEOW]: EggTier.COMMON, + [Species.CHINGLING]: EggTier.COMMON, + [Species.STUNKY]: EggTier.COMMON, + [Species.BRONZOR]: EggTier.COMMON, + [Species.BONSLY]: EggTier.COMMON, + [Species.MIME_JR]: EggTier.COMMON, + [Species.HAPPINY]: EggTier.COMMON, + [Species.CHATOT]: EggTier.COMMON, + [Species.SPIRITOMB]: EggTier.RARE, + [Species.GIBLE]: EggTier.RARE, + [Species.MUNCHLAX]: EggTier.RARE, + [Species.RIOLU]: EggTier.COMMON, + [Species.HIPPOPOTAS]: EggTier.COMMON, + [Species.SKORUPI]: EggTier.COMMON, + [Species.CROAGUNK]: EggTier.COMMON, + [Species.CARNIVINE]: EggTier.COMMON, + [Species.FINNEON]: EggTier.COMMON, + [Species.MANTYKE]: EggTier.COMMON, + [Species.SNOVER]: EggTier.COMMON, + [Species.ROTOM]: EggTier.RARE, + [Species.UXIE]: EggTier.EPIC, + [Species.MESPRIT]: EggTier.EPIC, + [Species.AZELF]: EggTier.EPIC, + [Species.DIALGA]: EggTier.LEGENDARY, + [Species.PALKIA]: EggTier.LEGENDARY, + [Species.HEATRAN]: EggTier.EPIC, + [Species.REGIGIGAS]: EggTier.EPIC, + [Species.GIRATINA]: EggTier.LEGENDARY, + [Species.CRESSELIA]: EggTier.EPIC, + [Species.PHIONE]: EggTier.RARE, + [Species.MANAPHY]: EggTier.EPIC, + [Species.DARKRAI]: EggTier.EPIC, + [Species.SHAYMIN]: EggTier.EPIC, + [Species.ARCEUS]: EggTier.LEGENDARY, + + [Species.VICTINI]: EggTier.EPIC, + [Species.SNIVY]: EggTier.COMMON, + [Species.TEPIG]: EggTier.COMMON, + [Species.OSHAWOTT]: EggTier.COMMON, + [Species.PATRAT]: EggTier.COMMON, + [Species.LILLIPUP]: EggTier.COMMON, + [Species.PURRLOIN]: EggTier.COMMON, + [Species.PANSAGE]: EggTier.COMMON, + [Species.PANSEAR]: EggTier.COMMON, + [Species.PANPOUR]: EggTier.COMMON, + [Species.MUNNA]: EggTier.COMMON, + [Species.PIDOVE]: EggTier.COMMON, + [Species.BLITZLE]: EggTier.COMMON, + [Species.ROGGENROLA]: EggTier.COMMON, + [Species.WOOBAT]: EggTier.COMMON, + [Species.DRILBUR]: EggTier.RARE, + [Species.AUDINO]: EggTier.COMMON, + [Species.TIMBURR]: EggTier.RARE, + [Species.TYMPOLE]: EggTier.COMMON, + [Species.THROH]: EggTier.RARE, + [Species.SAWK]: EggTier.RARE, + [Species.SEWADDLE]: EggTier.COMMON, + [Species.VENIPEDE]: EggTier.COMMON, + [Species.COTTONEE]: EggTier.COMMON, + [Species.PETILIL]: EggTier.COMMON, + [Species.BASCULIN]: EggTier.RARE, + [Species.SANDILE]: EggTier.RARE, + [Species.DARUMAKA]: EggTier.RARE, + [Species.MARACTUS]: EggTier.COMMON, + [Species.DWEBBLE]: EggTier.COMMON, + [Species.SCRAGGY]: EggTier.COMMON, + [Species.SIGILYPH]: EggTier.RARE, + [Species.YAMASK]: EggTier.COMMON, + [Species.TIRTOUGA]: EggTier.COMMON, + [Species.ARCHEN]: EggTier.COMMON, + [Species.TRUBBISH]: EggTier.COMMON, + [Species.ZORUA]: EggTier.COMMON, + [Species.MINCCINO]: EggTier.COMMON, + [Species.GOTHITA]: EggTier.COMMON, + [Species.SOLOSIS]: EggTier.COMMON, + [Species.DUCKLETT]: EggTier.COMMON, + [Species.VANILLITE]: EggTier.COMMON, + [Species.DEERLING]: EggTier.COMMON, + [Species.EMOLGA]: EggTier.COMMON, + [Species.KARRABLAST]: EggTier.COMMON, + [Species.FOONGUS]: EggTier.COMMON, + [Species.FRILLISH]: EggTier.COMMON, + [Species.ALOMOMOLA]: EggTier.RARE, + [Species.JOLTIK]: EggTier.COMMON, + [Species.FERROSEED]: EggTier.COMMON, + [Species.KLINK]: EggTier.COMMON, + [Species.TYNAMO]: EggTier.COMMON, + [Species.ELGYEM]: EggTier.COMMON, + [Species.LITWICK]: EggTier.COMMON, + [Species.AXEW]: EggTier.RARE, + [Species.CUBCHOO]: EggTier.COMMON, + [Species.CRYOGONAL]: EggTier.RARE, + [Species.SHELMET]: EggTier.COMMON, + [Species.STUNFISK]: EggTier.COMMON, + [Species.MIENFOO]: EggTier.COMMON, + [Species.DRUDDIGON]: EggTier.RARE, + [Species.GOLETT]: EggTier.COMMON, + [Species.PAWNIARD]: EggTier.RARE, + [Species.BOUFFALANT]: EggTier.RARE, + [Species.RUFFLET]: EggTier.COMMON, + [Species.VULLABY]: EggTier.COMMON, + [Species.HEATMOR]: EggTier.COMMON, + [Species.DURANT]: EggTier.RARE, + [Species.DEINO]: EggTier.RARE, + [Species.LARVESTA]: EggTier.RARE, + [Species.COBALION]: EggTier.EPIC, + [Species.TERRAKION]: EggTier.EPIC, + [Species.VIRIZION]: EggTier.EPIC, + [Species.TORNADUS]: EggTier.EPIC, + [Species.THUNDURUS]: EggTier.EPIC, + [Species.RESHIRAM]: EggTier.LEGENDARY, + [Species.ZEKROM]: EggTier.LEGENDARY, + [Species.LANDORUS]: EggTier.EPIC, + [Species.KYUREM]: EggTier.LEGENDARY, + [Species.KELDEO]: EggTier.EPIC, + [Species.MELOETTA]: EggTier.EPIC, + [Species.GENESECT]: EggTier.EPIC, + + [Species.CHESPIN]: EggTier.COMMON, + [Species.FENNEKIN]: EggTier.COMMON, + [Species.FROAKIE]: EggTier.RARE, + [Species.BUNNELBY]: EggTier.COMMON, + [Species.FLETCHLING]: EggTier.COMMON, + [Species.SCATTERBUG]: EggTier.COMMON, + [Species.LITLEO]: EggTier.COMMON, + [Species.FLABEBE]: EggTier.COMMON, + [Species.SKIDDO]: EggTier.COMMON, + [Species.PANCHAM]: EggTier.COMMON, + [Species.FURFROU]: EggTier.COMMON, + [Species.ESPURR]: EggTier.COMMON, + [Species.HONEDGE]: EggTier.RARE, + [Species.SPRITZEE]: EggTier.COMMON, + [Species.SWIRLIX]: EggTier.COMMON, + [Species.INKAY]: EggTier.COMMON, + [Species.BINACLE]: EggTier.COMMON, + [Species.SKRELP]: EggTier.COMMON, + [Species.CLAUNCHER]: EggTier.COMMON, + [Species.HELIOPTILE]: EggTier.COMMON, + [Species.TYRUNT]: EggTier.COMMON, + [Species.AMAURA]: EggTier.COMMON, + [Species.HAWLUCHA]: EggTier.RARE, + [Species.DEDENNE]: EggTier.COMMON, + [Species.CARBINK]: EggTier.COMMON, + [Species.GOOMY]: EggTier.RARE, + [Species.KLEFKI]: EggTier.COMMON, + [Species.PHANTUMP]: EggTier.COMMON, + [Species.PUMPKABOO]: EggTier.COMMON, + [Species.BERGMITE]: EggTier.COMMON, + [Species.NOIBAT]: EggTier.COMMON, + [Species.XERNEAS]: EggTier.LEGENDARY, + [Species.YVELTAL]: EggTier.LEGENDARY, + [Species.ZYGARDE]: EggTier.LEGENDARY, + [Species.DIANCIE]: EggTier.EPIC, + [Species.HOOPA]: EggTier.EPIC, + [Species.VOLCANION]: EggTier.EPIC, + [Species.ETERNAL_FLOETTE]: EggTier.RARE, + + [Species.ROWLET]: EggTier.COMMON, + [Species.LITTEN]: EggTier.COMMON, + [Species.POPPLIO]: EggTier.RARE, + [Species.PIKIPEK]: EggTier.COMMON, + [Species.YUNGOOS]: EggTier.COMMON, + [Species.GRUBBIN]: EggTier.COMMON, + [Species.CRABRAWLER]: EggTier.COMMON, + [Species.ORICORIO]: EggTier.COMMON, + [Species.CUTIEFLY]: EggTier.COMMON, + [Species.ROCKRUFF]: EggTier.COMMON, + [Species.WISHIWASHI]: EggTier.COMMON, + [Species.MAREANIE]: EggTier.COMMON, + [Species.MUDBRAY]: EggTier.COMMON, + [Species.DEWPIDER]: EggTier.COMMON, + [Species.FOMANTIS]: EggTier.COMMON, + [Species.MORELULL]: EggTier.COMMON, + [Species.SALANDIT]: EggTier.COMMON, + [Species.STUFFUL]: EggTier.COMMON, + [Species.BOUNSWEET]: EggTier.COMMON, + [Species.COMFEY]: EggTier.RARE, + [Species.ORANGURU]: EggTier.RARE, + [Species.PASSIMIAN]: EggTier.RARE, + [Species.WIMPOD]: EggTier.COMMON, + [Species.SANDYGAST]: EggTier.COMMON, + [Species.PYUKUMUKU]: EggTier.COMMON, + [Species.TYPE_NULL]: EggTier.RARE, + [Species.MINIOR]: EggTier.RARE, + [Species.KOMALA]: EggTier.COMMON, + [Species.TURTONATOR]: EggTier.RARE, + [Species.TOGEDEMARU]: EggTier.COMMON, + [Species.MIMIKYU]: EggTier.RARE, + [Species.BRUXISH]: EggTier.RARE, + [Species.DRAMPA]: EggTier.RARE, + [Species.DHELMISE]: EggTier.RARE, + [Species.JANGMO_O]: EggTier.RARE, + [Species.TAPU_KOKO]: EggTier.EPIC, + [Species.TAPU_LELE]: EggTier.EPIC, + [Species.TAPU_BULU]: EggTier.EPIC, + [Species.TAPU_FINI]: EggTier.EPIC, + [Species.COSMOG]: EggTier.EPIC, + [Species.NIHILEGO]: EggTier.EPIC, + [Species.BUZZWOLE]: EggTier.EPIC, + [Species.PHEROMOSA]: EggTier.EPIC, + [Species.XURKITREE]: EggTier.EPIC, + [Species.CELESTEELA]: EggTier.EPIC, + [Species.KARTANA]: EggTier.EPIC, + [Species.GUZZLORD]: EggTier.EPIC, + [Species.NECROZMA]: EggTier.LEGENDARY, + [Species.MAGEARNA]: EggTier.EPIC, + [Species.MARSHADOW]: EggTier.EPIC, + [Species.POIPOLE]: EggTier.EPIC, + [Species.STAKATAKA]: EggTier.EPIC, + [Species.BLACEPHALON]: EggTier.EPIC, + [Species.ZERAORA]: EggTier.EPIC, + [Species.MELTAN]: EggTier.EPIC, + [Species.ALOLA_RATTATA]: EggTier.COMMON, + [Species.ALOLA_SANDSHREW]: EggTier.COMMON, + [Species.ALOLA_VULPIX]: EggTier.COMMON, + [Species.ALOLA_DIGLETT]: EggTier.COMMON, + [Species.ALOLA_MEOWTH]: EggTier.COMMON, + [Species.ALOLA_GEODUDE]: EggTier.COMMON, + [Species.ALOLA_GRIMER]: EggTier.COMMON, + + [Species.GROOKEY]: EggTier.COMMON, + [Species.SCORBUNNY]: EggTier.RARE, + [Species.SOBBLE]: EggTier.COMMON, + [Species.SKWOVET]: EggTier.COMMON, + [Species.ROOKIDEE]: EggTier.COMMON, + [Species.BLIPBUG]: EggTier.COMMON, + [Species.NICKIT]: EggTier.COMMON, + [Species.GOSSIFLEUR]: EggTier.COMMON, + [Species.WOOLOO]: EggTier.COMMON, + [Species.CHEWTLE]: EggTier.COMMON, + [Species.YAMPER]: EggTier.COMMON, + [Species.ROLYCOLY]: EggTier.COMMON, + [Species.APPLIN]: EggTier.COMMON, + [Species.SILICOBRA]: EggTier.COMMON, + [Species.CRAMORANT]: EggTier.COMMON, + [Species.ARROKUDA]: EggTier.COMMON, + [Species.TOXEL]: EggTier.COMMON, + [Species.SIZZLIPEDE]: EggTier.COMMON, + [Species.CLOBBOPUS]: EggTier.COMMON, + [Species.SINISTEA]: EggTier.COMMON, + [Species.HATENNA]: EggTier.COMMON, + [Species.IMPIDIMP]: EggTier.COMMON, + [Species.MILCERY]: EggTier.COMMON, + [Species.FALINKS]: EggTier.RARE, + [Species.PINCURCHIN]: EggTier.COMMON, + [Species.SNOM]: EggTier.COMMON, + [Species.STONJOURNER]: EggTier.COMMON, + [Species.EISCUE]: EggTier.COMMON, + [Species.INDEEDEE]: EggTier.RARE, + [Species.MORPEKO]: EggTier.COMMON, + [Species.CUFANT]: EggTier.COMMON, + [Species.DRACOZOLT]: EggTier.RARE, + [Species.ARCTOZOLT]: EggTier.RARE, + [Species.DRACOVISH]: EggTier.RARE, + [Species.ARCTOVISH]: EggTier.RARE, + [Species.DURALUDON]: EggTier.RARE, + [Species.DREEPY]: EggTier.RARE, + [Species.ZACIAN]: EggTier.LEGENDARY, + [Species.ZAMAZENTA]: EggTier.LEGENDARY, + [Species.ETERNATUS]: EggTier.COMMON, + [Species.KUBFU]: EggTier.EPIC, + [Species.ZARUDE]: EggTier.EPIC, + [Species.REGIELEKI]: EggTier.EPIC, + [Species.REGIDRAGO]: EggTier.EPIC, + [Species.GLASTRIER]: EggTier.EPIC, + [Species.SPECTRIER]: EggTier.EPIC, + [Species.CALYREX]: EggTier.LEGENDARY, + [Species.GALAR_MEOWTH]: EggTier.COMMON, + [Species.GALAR_PONYTA]: EggTier.COMMON, + [Species.GALAR_SLOWPOKE]: EggTier.COMMON, + [Species.GALAR_FARFETCHD]: EggTier.COMMON, + [Species.GALAR_CORSOLA]: EggTier.COMMON, + [Species.GALAR_ZIGZAGOON]: EggTier.COMMON, + [Species.GALAR_DARUMAKA]: EggTier.RARE, + [Species.GALAR_YAMASK]: EggTier.COMMON, + [Species.GALAR_STUNFISK]: EggTier.COMMON, + [Species.GALAR_MR_MIME]: EggTier.COMMON, + [Species.GALAR_ARTICUNO]: EggTier.EPIC, + [Species.GALAR_ZAPDOS]: EggTier.EPIC, + [Species.GALAR_MOLTRES]: EggTier.EPIC, + [Species.HISUI_GROWLITHE]: EggTier.RARE, + [Species.HISUI_VOLTORB]: EggTier.COMMON, + [Species.HISUI_QWILFISH]: EggTier.RARE, + [Species.HISUI_SNEASEL]: EggTier.RARE, + [Species.HISUI_ZORUA]: EggTier.COMMON, + [Species.ENAMORUS]: EggTier.EPIC, + + [Species.SPRIGATITO]: EggTier.RARE, + [Species.FUECOCO]: EggTier.RARE, + [Species.QUAXLY]: EggTier.RARE, + [Species.LECHONK]: EggTier.COMMON, + [Species.TAROUNTULA]: EggTier.COMMON, + [Species.NYMBLE]: EggTier.COMMON, + [Species.PAWMI]: EggTier.COMMON, + [Species.TANDEMAUS]: EggTier.RARE, + [Species.FIDOUGH]: EggTier.COMMON, + [Species.SMOLIV]: EggTier.COMMON, + [Species.SQUAWKABILLY]: EggTier.COMMON, + [Species.NACLI]: EggTier.RARE, + [Species.CHARCADET]: EggTier.RARE, + [Species.TADBULB]: EggTier.COMMON, + [Species.WATTREL]: EggTier.COMMON, + [Species.MASCHIFF]: EggTier.COMMON, + [Species.SHROODLE]: EggTier.COMMON, + [Species.BRAMBLIN]: EggTier.COMMON, + [Species.TOEDSCOOL]: EggTier.COMMON, + [Species.KLAWF]: EggTier.COMMON, + [Species.CAPSAKID]: EggTier.COMMON, + [Species.RELLOR]: EggTier.COMMON, + [Species.FLITTLE]: EggTier.COMMON, + [Species.TINKATINK]: EggTier.RARE, + [Species.WIGLETT]: EggTier.COMMON, + [Species.BOMBIRDIER]: EggTier.COMMON, + [Species.FINIZEN]: EggTier.COMMON, + [Species.VAROOM]: EggTier.RARE, + [Species.CYCLIZAR]: EggTier.RARE, + [Species.ORTHWORM]: EggTier.RARE, + [Species.GLIMMET]: EggTier.RARE, + [Species.GREAVARD]: EggTier.COMMON, + [Species.FLAMIGO]: EggTier.RARE, + [Species.CETODDLE]: EggTier.COMMON, + [Species.VELUZA]: EggTier.RARE, + [Species.DONDOZO]: EggTier.RARE, + [Species.TATSUGIRI]: EggTier.RARE, + [Species.GREAT_TUSK]: EggTier.EPIC, + [Species.SCREAM_TAIL]: EggTier.EPIC, + [Species.BRUTE_BONNET]: EggTier.EPIC, + [Species.FLUTTER_MANE]: EggTier.EPIC, + [Species.SLITHER_WING]: EggTier.EPIC, + [Species.SANDY_SHOCKS]: EggTier.EPIC, + [Species.IRON_TREADS]: EggTier.EPIC, + [Species.IRON_BUNDLE]: EggTier.EPIC, + [Species.IRON_HANDS]: EggTier.EPIC, + [Species.IRON_JUGULIS]: EggTier.EPIC, + [Species.IRON_MOTH]: EggTier.EPIC, + [Species.IRON_THORNS]: EggTier.EPIC, + [Species.FRIGIBAX]: EggTier.RARE, + [Species.GIMMIGHOUL]: EggTier.RARE, + [Species.WO_CHIEN]: EggTier.EPIC, + [Species.CHIEN_PAO]: EggTier.EPIC, + [Species.TING_LU]: EggTier.EPIC, + [Species.CHI_YU]: EggTier.EPIC, + [Species.ROARING_MOON]: EggTier.EPIC, + [Species.IRON_VALIANT]: EggTier.EPIC, + [Species.KORAIDON]: EggTier.LEGENDARY, + [Species.MIRAIDON]: EggTier.LEGENDARY, + [Species.WALKING_WAKE]: EggTier.EPIC, + [Species.IRON_LEAVES]: EggTier.EPIC, + [Species.POLTCHAGEIST]: EggTier.RARE, + [Species.OKIDOGI]: EggTier.EPIC, + [Species.MUNKIDORI]: EggTier.EPIC, + [Species.FEZANDIPITI]: EggTier.EPIC, + [Species.OGERPON]: EggTier.EPIC, + [Species.GOUGING_FIRE]: EggTier.EPIC, + [Species.RAGING_BOLT]: EggTier.EPIC, + [Species.IRON_BOULDER]: EggTier.EPIC, + [Species.IRON_CROWN]: EggTier.EPIC, + [Species.TERAPAGOS]: EggTier.LEGENDARY, + [Species.PECHARUNT]: EggTier.EPIC, + [Species.PALDEA_TAUROS]: EggTier.RARE, + [Species.PALDEA_WOOPER]: EggTier.COMMON, + [Species.BLOODMOON_URSALUNA]: EggTier.EPIC, +}; diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index 0fadd992309..d6a1f0c3eaf 100644 --- a/src/data/balance/starters.ts +++ b/src/data/balance/starters.ts @@ -2,6 +2,12 @@ import { Species } from "#enums/species"; export const POKERUS_STARTER_COUNT = 5; +// #region Friendship constants +export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 2; +export const FRIENDSHIP_GAIN_FROM_BATTLE = 2; +export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 5; +export const FRIENDSHIP_LOSS_FROM_FAINT = 10; + /** * Function to get the cumulative friendship threshold at which a candy is earned * @param starterCost The cost of the starter, found in {@linkcode speciesStarterCosts} @@ -9,25 +15,25 @@ export const POKERUS_STARTER_COUNT = 5; */ export function getStarterValueFriendshipCap(starterCost: number): number { switch (starterCost) { - case 1: - return 20; - case 2: - return 40; - case 3: - return 60; - case 4: - return 100; - case 5: - return 140; - case 6: - return 200; - case 7: - return 280; - case 8: - case 9: - return 450; - default: - return 600; + case 1: + return 20; + case 2: + return 40; + case 3: + return 60; + case 4: + return 100; + case 5: + return 140; + case 6: + return 200; + case 7: + return 280; + case 8: + case 9: + return 450; + default: + return 600; } } diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 939f834cccc..03bf0809fa6 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -134,15 +134,15 @@ export class AnimConfig { for (const te of frameTimedEvents[fte]) { let timedEvent: AnimTimedEvent | undefined; switch (te.eventType) { - case "AnimTimedSoundEvent": - timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te); - break; - case "AnimTimedAddBgEvent": - timedEvent = new AnimTimedAddBgEvent(te.frameIndex, te.resourceName, te); - break; - case "AnimTimedUpdateBgEvent": - timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te); - break; + case "AnimTimedSoundEvent": + timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te); + break; + case "AnimTimedAddBgEvent": + timedEvent = new AnimTimedAddBgEvent(te.frameIndex, te.resourceName, te); + break; + case "AnimTimedUpdateBgEvent": + timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te); + break; } timedEvent && timedEvents.push(timedEvent); @@ -243,12 +243,12 @@ class AnimFrame { if (!init) { let target = AnimFrameTarget.GRAPHIC; switch (pattern) { - case -2: - target = AnimFrameTarget.TARGET; - break; - case -1: - target = AnimFrameTarget.USER; - break; + case -2: + target = AnimFrameTarget.TARGET; + break; + case -1: + target = AnimFrameTarget.USER; + break; } this.target = target; this.graphicFrame = pattern >= 0 ? pattern : 0; @@ -803,23 +803,23 @@ export abstract class BattleAnim { let scaleX = (frame.zoomX / 100) * (!frame.mirror ? 1 : -1); const scaleY = (frame.zoomY / 100); switch (frame.focus) { - case AnimFocus.TARGET: - x += targetInitialX - targetFocusX; - y += (targetInitialY - targetHalfHeight) - targetFocusY; - break; - case AnimFocus.USER: - x += userInitialX - userFocusX; - y += (userInitialY - userHalfHeight) - userFocusY; - break; - case AnimFocus.USER_TARGET: - const point = transformPoint(this.srcLine[0], this.srcLine[1], this.srcLine[2], this.srcLine[3], - this.dstLine[0], this.dstLine[1] - userHalfHeight, this.dstLine[2], this.dstLine[3] - targetHalfHeight, x, y); - x = point[0]; - y = point[1]; - if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) { - scaleX = scaleX * -1; - } - break; + case AnimFocus.TARGET: + x += targetInitialX - targetFocusX; + y += (targetInitialY - targetHalfHeight) - targetFocusY; + break; + case AnimFocus.USER: + x += userInitialX - userFocusX; + y += (userInitialY - userHalfHeight) - userFocusY; + break; + case AnimFocus.USER_TARGET: + const point = transformPoint(this.srcLine[0], this.srcLine[1], this.srcLine[2], this.srcLine[3], + this.dstLine[0], this.dstLine[1] - userHalfHeight, this.dstLine[2], this.dstLine[3] - targetHalfHeight, x, y); + x = point[0]; + y = point[1]; + if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) { + scaleX = scaleX * -1; + } + break; } const angle = -frame.angle; const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++; @@ -993,44 +993,44 @@ export abstract class BattleAnim { spritePriorities[graphicIndex] = frame.priority; const setSpritePriority = (priority: integer) => { switch (priority) { - case 0: - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon()!); // This bang assumes that if (the EnemyPokemon is undefined, then the PlayerPokemon function must return an object), correct assumption? - break; - case 1: - scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); - break; - case 2: - switch (frame.focus) { - case AnimFocus.USER: - if (this.bgSprite) { - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); - } else { - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? + case 0: + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon()!); // This bang assumes that if (the EnemyPokemon is undefined, then the PlayerPokemon function must return an object), correct assumption? + break; + case 1: + scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); + break; + case 2: + switch (frame.focus) { + case AnimFocus.USER: + if (this.bgSprite) { + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); + } else { + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? + } + break; + case AnimFocus.TARGET: + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? + break; + default: + setSpritePriority(1); + break; } break; - case AnimFocus.TARGET: - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? + case 3: + switch (frame.focus) { + case AnimFocus.USER: + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? + break; + case AnimFocus.TARGET: + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? + break; + default: + setSpritePriority(1); + break; + } break; default: setSpritePriority(1); - break; - } - break; - case 3: - switch (frame.focus) { - case AnimFocus.USER: - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? - break; - case AnimFocus.TARGET: - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? - break; - default: - setSpritePriority(1); - break; - } - break; - default: - setSpritePriority(1); } }; setSpritePriority(frame.priority); @@ -1396,108 +1396,108 @@ export async function populateAnims() { const fieldName = field.slice(0, field.indexOf(":")); const fieldData = field.slice(fieldName.length + 1, field.lastIndexOf("\n")).trim(); switch (fieldName) { - case "array": - const framesData = fieldData.split(" - - - ").slice(1); - for (let fd = 0; fd < framesData.length; fd++) { - anim.frames.push([]); - const frameData = framesData[fd]; - const focusFramesData = frameData.split(" - - "); - for (let tf = 0; tf < focusFramesData.length; tf++) { - const values = focusFramesData[tf].replace(/ \- /g, "").split("\n"); - const targetFrame = new AnimFrame(parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[11]), parseFloat(values[3]), - parseInt(values[4]) === 1, parseInt(values[6]) === 1, parseInt(values[5]), parseInt(values[7]), parseInt(values[8]), parseInt(values[12]), parseInt(values[13]), - parseInt(values[14]), parseInt(values[15]), parseInt(values[16]), parseInt(values[17]), parseInt(values[18]), parseInt(values[19]), - parseInt(values[21]), parseInt(values[22]), parseInt(values[23]), parseInt(values[24]), parseInt(values[20]) === 1, parseInt(values[25]), parseInt(values[26]) as AnimFocus); - anim.frames[fd].push(targetFrame); + case "array": + const framesData = fieldData.split(" - - - ").slice(1); + for (let fd = 0; fd < framesData.length; fd++) { + anim.frames.push([]); + const frameData = framesData[fd]; + const focusFramesData = frameData.split(" - - "); + for (let tf = 0; tf < focusFramesData.length; tf++) { + const values = focusFramesData[tf].replace(/ \- /g, "").split("\n"); + const targetFrame = new AnimFrame(parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[11]), parseFloat(values[3]), + parseInt(values[4]) === 1, parseInt(values[6]) === 1, parseInt(values[5]), parseInt(values[7]), parseInt(values[8]), parseInt(values[12]), parseInt(values[13]), + parseInt(values[14]), parseInt(values[15]), parseInt(values[16]), parseInt(values[17]), parseInt(values[18]), parseInt(values[19]), + parseInt(values[21]), parseInt(values[22]), parseInt(values[23]), parseInt(values[24]), parseInt(values[20]) === 1, parseInt(values[25]), parseInt(values[26]) as AnimFocus); + anim.frames[fd].push(targetFrame); + } } - } - break; - case "graphic": - const graphic = fieldData !== "''" ? fieldData : ""; - anim.graphic = graphic.indexOf(".") > -1 - ? graphic.slice(0, fieldData.indexOf(".")) - : graphic; - break; - case "timing": - const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1); - for (let t = 0; t < timingEntries.length; t++) { - const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",") - .replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1"); - const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct? - let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct? - const timingType = parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct? - let timedEvent: AnimTimedEvent | undefined; - switch (timingType) { - case 0: - if (resourceName && resourceName.indexOf(".") === -1) { - let ext: string | undefined; - [ "wav", "mp3", "m4a" ].every(e => { - if (seNames.indexOf(`${resourceName}.${e}`) > -1) { - ext = e; - return false; + break; + case "graphic": + const graphic = fieldData !== "''" ? fieldData : ""; + anim.graphic = graphic.indexOf(".") > -1 + ? graphic.slice(0, fieldData.indexOf(".")) + : graphic; + break; + case "timing": + const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1); + for (let t = 0; t < timingEntries.length; t++) { + const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",") + .replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1"); + const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct? + let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct? + const timingType = parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct? + let timedEvent: AnimTimedEvent | undefined; + switch (timingType) { + case 0: + if (resourceName && resourceName.indexOf(".") === -1) { + let ext: string | undefined; + [ "wav", "mp3", "m4a" ].every(e => { + if (seNames.indexOf(`${resourceName}.${e}`) > -1) { + ext = e; + return false; + } + return true; + }); + if (!ext) { + ext = ".wav"; + } + resourceName += `.${ext}`; } - return true; - }); - if (!ext) { - ext = ".wav"; + timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName); + break; + case 1: + timedEvent = new AnimTimedAddBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); + break; + case 2: + timedEvent = new AnimTimedUpdateBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); + break; + } + if (!timedEvent) { + continue; + } + const propPattern = /([a-z]+): (.*?)(?:,|\})/ig; + let propMatch: RegExpExecArray; + while ((propMatch = propPattern.exec(timingData)!)) { // TODO: is this bang correct? + const prop = propMatch[1]; + let value: any = propMatch[2]; + switch (prop) { + case "bgX": + case "bgY": + value = parseFloat(value); + break; + case "volume": + case "pitch": + case "opacity": + case "colorRed": + case "colorGreen": + case "colorBlue": + case "colorAlpha": + case "duration": + case "flashScope": + case "flashRed": + case "flashGreen": + case "flashBlue": + case "flashAlpha": + case "flashDuration": + value = parseInt(value); + break; + } + if (timedEvent.hasOwnProperty(prop)) { + timedEvent[prop] = value; } - resourceName += `.${ext}`; } - timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName); - break; - case 1: - timedEvent = new AnimTimedAddBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); - break; - case 2: - timedEvent = new AnimTimedUpdateBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); - break; - } - if (!timedEvent) { - continue; - } - const propPattern = /([a-z]+): (.*?)(?:,|\})/ig; - let propMatch: RegExpExecArray; - while ((propMatch = propPattern.exec(timingData)!)) { // TODO: is this bang correct? - const prop = propMatch[1]; - let value: any = propMatch[2]; - switch (prop) { - case "bgX": - case "bgY": - value = parseFloat(value); - break; - case "volume": - case "pitch": - case "opacity": - case "colorRed": - case "colorGreen": - case "colorBlue": - case "colorAlpha": - case "duration": - case "flashScope": - case "flashRed": - case "flashGreen": - case "flashBlue": - case "flashAlpha": - case "flashDuration": - value = parseInt(value); - break; + if (!anim.frameTimedEvents.has(frameIndex)) { + anim.frameTimedEvents.set(frameIndex, []); } - if (timedEvent.hasOwnProperty(prop)) { - timedEvent[prop] = value; - } - } - if (!anim.frameTimedEvents.has(frameIndex)) { - anim.frameTimedEvents.set(frameIndex, []); - } anim.frameTimedEvents.get(frameIndex)!.push(timedEvent); // TODO: is this bang correct? - } - break; - case "position": - anim.position = parseInt(fieldData); - break; - case "hue": - anim.hue = parseInt(fieldData); - break; + } + break; + case "position": + anim.position = parseInt(fieldData); + break; + case "hue": + anim.hue = parseInt(fieldData); + break; } } } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index a54a8c5f519..4977a8da5a9 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -23,6 +23,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { StatStageChangePhase, StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; import { PokemonAnimType } from "#app/enums/pokemon-anim-type"; +import BattleScene from "#app/battle-scene"; export enum BattlerTagLapseType { FAINT, @@ -90,6 +91,15 @@ export class BattlerTag { this.sourceMove = source.sourceMove; this.sourceId = source.sourceId; } + + /** + * Helper function that retrieves the source Pokemon object + * @param scene medium to retrieve the source Pokemon + * @returns The source {@linkcode Pokemon} or `null` if none is found + */ + public getSourcePokemon(scene: BattleScene): Pokemon | null { + return this.sourceId ? scene.getPokemonById(this.sourceId) : null; + } } export interface WeatherBattlerTag { @@ -120,7 +130,7 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { const phase = pokemon.scene.getCurrentPhase() as MovePhase; const move = phase.move; - if (this.isMoveRestricted(move.moveId)) { + if (this.isMoveRestricted(move.moveId, pokemon)) { if (this.interruptedText(pokemon, move.moveId)) { pokemon.scene.queueMessage(this.interruptedText(pokemon, move.moveId)); } @@ -136,10 +146,11 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { /** * Gets whether this tag is restricting a move. * - * @param {Moves} move {@linkcode Moves} ID to check restriction for. - * @returns {boolean} `true` if the move is restricted by this tag, otherwise `false`. + * @param move - {@linkcode Moves} ID to check restriction for. + * @param user - The {@linkcode Pokemon} involved + * @returns `true` if the move is restricted by this tag, otherwise `false`. */ - abstract isMoveRestricted(move: Moves): boolean; + public abstract isMoveRestricted(move: Moves, user?: Pokemon): boolean; /** * Checks if this tag is restricting a move based on a user's decisions during the target selection phase @@ -327,6 +338,16 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag { pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false); } + /** + * Loads the Gorilla Tactics Battler Tag along with its unique class variable moveId + * @override + * @param source Gorilla Tactics' {@linkcode BattlerTag} information + */ + public override loadTag(source: BattlerTag | any): void { + super.loadTag(source); + this.moveId = source.moveId; + } + /** * * @override @@ -898,14 +919,14 @@ export class EncoreTag extends BattlerTag { } switch (repeatableMove.move) { - case Moves.MIMIC: - case Moves.MIRROR_MOVE: - case Moves.TRANSFORM: - case Moves.STRUGGLE: - case Moves.SKETCH: - case Moves.SLEEP_TALK: - case Moves.ENCORE: - return false; + case Moves.MIMIC: + case Moves.MIRROR_MOVE: + case Moves.TRANSFORM: + case Moves.STRUGGLE: + case Moves.SKETCH: + case Moves.SLEEP_TALK: + case Moves.ENCORE: + return false; } if (allMoves[repeatableMove.move].hasAttr(ChargeAttr) && repeatableMove.result === MoveResult.OTHER) { @@ -1376,7 +1397,7 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag { const effectPhase = pokemon.scene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels)); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ this.stat ], this.levels)); } } @@ -1620,12 +1641,12 @@ export class HighestStatBoostTag extends AbilityBattlerTag { this.stat = highestStat; switch (this.stat) { - case Stat.SPD: - this.multiplier = 1.5; - break; - default: - this.multiplier = 1.3; - break; + case Stat.SPD: + this.multiplier = 1.5; + break; + default: + this.multiplier = 1.3; + break; } pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true); @@ -1713,7 +1734,12 @@ export class TypeImmuneTag extends BattlerTag { } } -export class MagnetRisenTag extends TypeImmuneTag { +/** + * Battler Tag that lifts the affected Pokemon into the air and provides immunity to Ground type moves. + * @see {@link https://bulbapedia.bulbagarden.net/wiki/Magnet_Rise_(move) | Moves.MAGNET_RISE} + * @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | Moves.TELEKINESIS} + */ +export class FloatingTag extends TypeImmuneTag { constructor(tagType: BattlerTagType, sourceMove: Moves) { super(tagType, sourceMove, Type.GROUND, 5); } @@ -1721,13 +1747,17 @@ export class MagnetRisenTag extends TypeImmuneTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + if (this.sourceMove === Moves.MAGNET_RISE) { + pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } + } onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - - pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + if (this.sourceMove === Moves.MAGNET_RISE) { + pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } } } @@ -2139,6 +2169,10 @@ export class GulpMissileTag extends BattlerTag { return false; } + if (moveEffectPhase.move.getMove().hitsSubstitute(attacker, pokemon)) { + return true; + } + const cancelled = new Utils.BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); @@ -2402,15 +2436,15 @@ export class SubstituteTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { switch (lapseType) { - case BattlerTagLapseType.PRE_MOVE: - this.onPreMove(pokemon); - break; - case BattlerTagLapseType.AFTER_MOVE: - this.onAfterMove(pokemon); - break; - case BattlerTagLapseType.HIT: - this.onHit(pokemon); - break; + case BattlerTagLapseType.PRE_MOVE: + this.onPreMove(pokemon); + break; + case BattlerTagLapseType.AFTER_MOVE: + this.onAfterMove(pokemon); + break; + case BattlerTagLapseType.HIT: + this.onHit(pokemon); + break; } return lapseType !== BattlerTagLapseType.CUSTOM; // only remove this tag on custom lapse } @@ -2497,8 +2531,6 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { * Torment does not interrupt the move if the move is performed consecutively in the same turn and right after Torment is applied */ export class TormentTag extends MoveRestrictionBattlerTag { - private target: Pokemon; - constructor(sourceId: number) { super(BattlerTagType.TORMENT, BattlerTagLapseType.AFTER_MOVE, 1, Moves.TORMENT, sourceId); } @@ -2510,7 +2542,6 @@ export class TormentTag extends MoveRestrictionBattlerTag { */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - this.target = pokemon; pokemon.scene.queueMessage(i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); } @@ -2529,15 +2560,18 @@ export class TormentTag extends MoveRestrictionBattlerTag { * @param {Moves} move the move under investigation * @returns `true` if there is valid consecutive usage | `false` if the moves are different from each other */ - override isMoveRestricted(move: Moves): boolean { - const lastMove = this.target.getLastXMoves(1)[0]; + public override isMoveRestricted(move: Moves, user: Pokemon): boolean { + if (!user) { + return false; + } + const lastMove = user.getLastXMoves(1)[0]; if ( !lastMove ) { return false; } // This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY // Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts const moveObj = allMoves[lastMove.move]; - const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || this.target.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr); + const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr); const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS); if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isUnaffected) { return true; @@ -2589,37 +2623,39 @@ export class TauntTag extends MoveRestrictionBattlerTag { * The tag is only removed when the source-user is removed from the field. */ export class ImprisonTag extends MoveRestrictionBattlerTag { - private source: Pokemon | null; - constructor(sourceId: number) { super(BattlerTagType.IMPRISON, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE ], 1, Moves.IMPRISON, sourceId); } - override onAdd(pokemon: Pokemon) { - if (this.sourceId) { - this.source = pokemon.scene.getPokemonById(this.sourceId); - } - } - /** * Checks if the source of Imprison is still active - * @param _pokemon - * @param _lapseType + * @override + * @param pokemon The pokemon this tag is attached to * @returns `true` if the source is still active */ - override lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { - return this.source?.isActive(true) ?? false; + public override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { + const source = this.getSourcePokemon(pokemon.scene); + if (source) { + if (lapseType === BattlerTagLapseType.PRE_MOVE) { + return super.lapse(pokemon, lapseType) && source.isActive(true); + } else { + return source.isActive(true); + } + } + return false; } /** * Checks if the source of the tag has the parameter move in its moveset and that the source is still active + * @override * @param {Moves} move the move under investigation * @returns `false` if either condition is not met */ - override isMoveRestricted(move: Moves): boolean { - if (this.source) { - const sourceMoveset = this.source.getMoveset().map(m => m!.moveId); - return sourceMoveset?.includes(move) && this.source.isActive(true); + public override isMoveRestricted(move: Moves, user: Pokemon): boolean { + const source = this.getSourcePokemon(user.scene); + if (source) { + const sourceMoveset = source.getMoveset().map(m => m!.moveId); + return sourceMoveset?.includes(move) && source.isActive(true); } return false; } @@ -2636,16 +2672,16 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { /** * Battler Tag that applies the effects of Syrup Bomb to the target Pokemon. * For three turns, starting from the turn of hit, at the end of each turn, the target Pokemon's speed will decrease by 1. - * The tag can also expire by taking the target Pokemon off the field. + * The tag can also expire by taking the target Pokemon off the field, or the Pokemon that originally used the move. */ export class SyrupBombTag extends BattlerTag { - constructor() { - super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB); + constructor(sourceId: number) { + super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB, sourceId); } /** * Adds the Syrup Bomb battler tag to the target Pokemon. - * @param {Pokemon} pokemon the target Pokemon + * @param pokemon - The target {@linkcode Pokemon} */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); @@ -2654,15 +2690,16 @@ export class SyrupBombTag extends BattlerTag { /** * Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count - * @param {Pokemon} pokemon the target Pokemon - * @param {BattlerTagLapseType} _lapseType - * @returns `true` if the turnCount is still greater than 0 | `false` if the turnCount is 0 or the target Pokemon has been removed from the field + * @param pokemon - The target {@linkcode Pokemon} + * @param _lapseType - N/A + * @returns `true` if the `turnCount` is still greater than `0`; `false` if the `turnCount` is `0` or the target or source Pokemon has been removed from the field */ override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { - if (!pokemon.isActive(true)) { + if (this.sourceId && !pokemon.scene.getPokemonById(this.sourceId)?.isActive(true)) { return false; } - pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // Custom message in lieu of an animation in mainline + // Custom message in lieu of an animation in mainline + pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.unshiftPhase(new StatStageChangePhase( pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPD ], -1, true, false, true @@ -2671,186 +2708,240 @@ export class SyrupBombTag extends BattlerTag { } } +/** + * Telekinesis raises the target into the air for three turns and causes all moves used against the target (aside from OHKO moves) to hit the target unless the target is in a semi-invulnerable state from Fly/Dig. + * The first effect is provided by {@linkcode FloatingTag}, the accuracy-bypass effect is provided by TelekinesisTag + * The effects of Telekinesis can be baton passed to a teammate. Unlike the mainline games, Telekinesis can be baton-passed to Mega Gengar. + * @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | Moves.TELEKINESIS} + */ +export class TelekinesisTag extends BattlerTag { + constructor(sourceMove: Moves) { + super(BattlerTagType.TELEKINESIS, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE ], 3, sourceMove, undefined, true); + } + + override onAdd(pokemon: Pokemon) { + pokemon.scene.queueMessage(i18next.t("battlerTags:telekinesisOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } +} + +/** + * Tag that swaps the user's base ATK stat with its base DEF stat. + * @extends BattlerTag + */ +export class PowerTrickTag extends BattlerTag { + constructor(sourceMove: Moves, sourceId: number) { + super(BattlerTagType.POWER_TRICK, BattlerTagLapseType.CUSTOM, 0, sourceMove, sourceId, true); + } + + onAdd(pokemon: Pokemon): void { + this.swapStat(pokemon); + pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } + + onRemove(pokemon: Pokemon): void { + this.swapStat(pokemon); + pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } + + /** + * Removes the Power Trick tag and reverts any stat changes if the tag is already applied. + * @param {Pokemon} pokemon The {@linkcode Pokemon} that already has the Power Trick tag. + */ + onOverlap(pokemon: Pokemon): void { + pokemon.removeTag(this.tagType); + } + + /** + * Swaps the user's base ATK stat with its base DEF stat. + * @param {Pokemon} pokemon The {@linkcode Pokemon} whose stats will be swapped. + */ + swapStat(pokemon: Pokemon): void { + const temp = pokemon.getStat(Stat.ATK, false); + pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.DEF, false), false); + pokemon.setStat(Stat.DEF, temp, false); + } +} + /** * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. - * - * @param {BattlerTagType} tagType the type of the {@linkcode BattlerTagType}. - * @param turnCount the turn count. - * @param {Moves} sourceMove the source {@linkcode Moves}. - * @param sourceId the source ID. - * @returns {BattlerTag} the corresponding {@linkcode BattlerTag} object. + * @param sourceId - The ID of the pokemon adding the tag + * @returns The corresponding {@linkcode BattlerTag} object. */ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag { switch (tagType) { - case BattlerTagType.RECHARGING: - return new RechargingTag(sourceMove); - case BattlerTagType.BEAK_BLAST_CHARGING: - return new BeakBlastChargingTag(); - case BattlerTagType.SHELL_TRAP: - return new ShellTrapTag(); - case BattlerTagType.FLINCHED: - return new FlinchedTag(sourceMove); - case BattlerTagType.INTERRUPTED: - return new InterruptedTag(sourceMove); - case BattlerTagType.CONFUSED: - return new ConfusedTag(turnCount, sourceMove); - case BattlerTagType.INFATUATED: - return new InfatuatedTag(sourceMove, sourceId); - case BattlerTagType.SEEDED: - return new SeedTag(sourceId); - case BattlerTagType.NIGHTMARE: - return new NightmareTag(); - case BattlerTagType.FRENZY: - return new FrenzyTag(turnCount, sourceMove, sourceId); - case BattlerTagType.CHARGING: - return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId); - case BattlerTagType.ENCORE: - return new EncoreTag(sourceId); - case BattlerTagType.HELPING_HAND: - return new HelpingHandTag(sourceId); - case BattlerTagType.INGRAIN: - return new IngrainTag(sourceId); - case BattlerTagType.AQUA_RING: - return new AquaRingTag(); - case BattlerTagType.DROWSY: - return new DrowsyTag(); - case BattlerTagType.TRAPPED: - return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); - case BattlerTagType.NO_RETREAT: - return new NoRetreatTag(sourceId); - case BattlerTagType.BIND: - return new BindTag(turnCount, sourceId); - case BattlerTagType.WRAP: - return new WrapTag(turnCount, sourceId); - case BattlerTagType.FIRE_SPIN: - return new FireSpinTag(turnCount, sourceId); - case BattlerTagType.WHIRLPOOL: - return new WhirlpoolTag(turnCount, sourceId); - case BattlerTagType.CLAMP: - return new ClampTag(turnCount, sourceId); - case BattlerTagType.SAND_TOMB: - return new SandTombTag(turnCount, sourceId); - case BattlerTagType.MAGMA_STORM: - return new MagmaStormTag(turnCount, sourceId); - case BattlerTagType.SNAP_TRAP: - return new SnapTrapTag(turnCount, sourceId); - case BattlerTagType.THUNDER_CAGE: - return new ThunderCageTag(turnCount, sourceId); - case BattlerTagType.INFESTATION: - return new InfestationTag(turnCount, sourceId); - case BattlerTagType.PROTECTED: - return new ProtectedTag(sourceMove); - case BattlerTagType.SPIKY_SHIELD: - return new ContactDamageProtectedTag(sourceMove, 8); - case BattlerTagType.KINGS_SHIELD: - return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1); - case BattlerTagType.OBSTRUCT: - return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2); - case BattlerTagType.SILK_TRAP: - return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1); - case BattlerTagType.BANEFUL_BUNKER: - return new ContactPoisonProtectedTag(sourceMove); - case BattlerTagType.BURNING_BULWARK: - return new ContactBurnProtectedTag(sourceMove); - case BattlerTagType.ENDURING: - return new EnduringTag(sourceMove); - case BattlerTagType.STURDY: - return new SturdyTag(sourceMove); - case BattlerTagType.PERISH_SONG: - return new PerishSongTag(turnCount); - case BattlerTagType.CENTER_OF_ATTENTION: - return new CenterOfAttentionTag(sourceMove); - case BattlerTagType.TRUANT: - return new TruantTag(); - case BattlerTagType.SLOW_START: - return new SlowStartTag(); - case BattlerTagType.PROTOSYNTHESIS: - return new WeatherHighestStatBoostTag(tagType, Abilities.PROTOSYNTHESIS, WeatherType.SUNNY, WeatherType.HARSH_SUN); - case BattlerTagType.QUARK_DRIVE: - return new TerrainHighestStatBoostTag(tagType, Abilities.QUARK_DRIVE, TerrainType.ELECTRIC); - case BattlerTagType.FLYING: - case BattlerTagType.UNDERGROUND: - case BattlerTagType.UNDERWATER: - case BattlerTagType.HIDDEN: - return new SemiInvulnerableTag(tagType, turnCount, sourceMove); - case BattlerTagType.FIRE_BOOST: - return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false); - case BattlerTagType.CRIT_BOOST: - return new CritBoostTag(tagType, sourceMove); - case BattlerTagType.DRAGON_CHEER: - return new DragonCheerTag(); - case BattlerTagType.ALWAYS_CRIT: - case BattlerTagType.IGNORE_ACCURACY: - return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove); - case BattlerTagType.ALWAYS_GET_HIT: - case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: - return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove); - case BattlerTagType.BYPASS_SLEEP: - return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove); - case BattlerTagType.IGNORE_FLYING: - return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); - case BattlerTagType.ROOSTED: - return new RoostedTag(); - case BattlerTagType.BURNED_UP: - return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); - case BattlerTagType.DOUBLE_SHOCKED: - return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); - case BattlerTagType.SALT_CURED: - return new SaltCuredTag(sourceId); - case BattlerTagType.CURSED: - return new CursedTag(sourceId); - case BattlerTagType.CHARGED: - return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true); - case BattlerTagType.MAGNET_RISEN: - return new MagnetRisenTag(tagType, sourceMove); - case BattlerTagType.MINIMIZED: - return new MinimizeTag(); - case BattlerTagType.DESTINY_BOND: - return new DestinyBondTag(sourceMove, sourceId); - case BattlerTagType.ICE_FACE: - return new IceFaceBlockDamageTag(tagType); - case BattlerTagType.DISGUISE: - return new FormBlockDamageTag(tagType); - case BattlerTagType.STOCKPILING: - return new StockpilingTag(sourceMove); - case BattlerTagType.OCTOLOCK: - return new OctolockTag(sourceId); - case BattlerTagType.DISABLED: - return new DisabledTag(sourceId); - case BattlerTagType.IGNORE_GHOST: - return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]); - case BattlerTagType.IGNORE_DARK: - return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]); - case BattlerTagType.GULP_MISSILE_ARROKUDA: - case BattlerTagType.GULP_MISSILE_PIKACHU: - return new GulpMissileTag(tagType, sourceMove); - case BattlerTagType.TAR_SHOT: - return new TarShotTag(); - case BattlerTagType.ELECTRIFIED: - return new ElectrifiedTag(); - case BattlerTagType.THROAT_CHOPPED: - return new ThroatChoppedTag(); - case BattlerTagType.GORILLA_TACTICS: - return new GorillaTacticsTag(); - case BattlerTagType.SUBSTITUTE: - return new SubstituteTag(sourceMove, sourceId); - case BattlerTagType.AUTOTOMIZED: - return new AutotomizedTag(); - case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON: - return new MysteryEncounterPostSummonTag(); - case BattlerTagType.HEAL_BLOCK: - return new HealBlockTag(turnCount, sourceMove); - case BattlerTagType.TORMENT: - return new TormentTag(sourceId); - case BattlerTagType.TAUNT: - return new TauntTag(); - case BattlerTagType.IMPRISON: - return new ImprisonTag(sourceId); - case BattlerTagType.SYRUP_BOMB: - return new SyrupBombTag(); - case BattlerTagType.NONE: - default: - return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); + case BattlerTagType.RECHARGING: + return new RechargingTag(sourceMove); + case BattlerTagType.BEAK_BLAST_CHARGING: + return new BeakBlastChargingTag(); + case BattlerTagType.SHELL_TRAP: + return new ShellTrapTag(); + case BattlerTagType.FLINCHED: + return new FlinchedTag(sourceMove); + case BattlerTagType.INTERRUPTED: + return new InterruptedTag(sourceMove); + case BattlerTagType.CONFUSED: + return new ConfusedTag(turnCount, sourceMove); + case BattlerTagType.INFATUATED: + return new InfatuatedTag(sourceMove, sourceId); + case BattlerTagType.SEEDED: + return new SeedTag(sourceId); + case BattlerTagType.NIGHTMARE: + return new NightmareTag(); + case BattlerTagType.FRENZY: + return new FrenzyTag(turnCount, sourceMove, sourceId); + case BattlerTagType.CHARGING: + return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId); + case BattlerTagType.ENCORE: + return new EncoreTag(sourceId); + case BattlerTagType.HELPING_HAND: + return new HelpingHandTag(sourceId); + case BattlerTagType.INGRAIN: + return new IngrainTag(sourceId); + case BattlerTagType.AQUA_RING: + return new AquaRingTag(); + case BattlerTagType.DROWSY: + return new DrowsyTag(); + case BattlerTagType.TRAPPED: + return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); + case BattlerTagType.NO_RETREAT: + return new NoRetreatTag(sourceId); + case BattlerTagType.BIND: + return new BindTag(turnCount, sourceId); + case BattlerTagType.WRAP: + return new WrapTag(turnCount, sourceId); + case BattlerTagType.FIRE_SPIN: + return new FireSpinTag(turnCount, sourceId); + case BattlerTagType.WHIRLPOOL: + return new WhirlpoolTag(turnCount, sourceId); + case BattlerTagType.CLAMP: + return new ClampTag(turnCount, sourceId); + case BattlerTagType.SAND_TOMB: + return new SandTombTag(turnCount, sourceId); + case BattlerTagType.MAGMA_STORM: + return new MagmaStormTag(turnCount, sourceId); + case BattlerTagType.SNAP_TRAP: + return new SnapTrapTag(turnCount, sourceId); + case BattlerTagType.THUNDER_CAGE: + return new ThunderCageTag(turnCount, sourceId); + case BattlerTagType.INFESTATION: + return new InfestationTag(turnCount, sourceId); + case BattlerTagType.PROTECTED: + return new ProtectedTag(sourceMove); + case BattlerTagType.SPIKY_SHIELD: + return new ContactDamageProtectedTag(sourceMove, 8); + case BattlerTagType.KINGS_SHIELD: + return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1); + case BattlerTagType.OBSTRUCT: + return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2); + case BattlerTagType.SILK_TRAP: + return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1); + case BattlerTagType.BANEFUL_BUNKER: + return new ContactPoisonProtectedTag(sourceMove); + case BattlerTagType.BURNING_BULWARK: + return new ContactBurnProtectedTag(sourceMove); + case BattlerTagType.ENDURING: + return new EnduringTag(sourceMove); + case BattlerTagType.STURDY: + return new SturdyTag(sourceMove); + case BattlerTagType.PERISH_SONG: + return new PerishSongTag(turnCount); + case BattlerTagType.CENTER_OF_ATTENTION: + return new CenterOfAttentionTag(sourceMove); + case BattlerTagType.TRUANT: + return new TruantTag(); + case BattlerTagType.SLOW_START: + return new SlowStartTag(); + case BattlerTagType.PROTOSYNTHESIS: + return new WeatherHighestStatBoostTag(tagType, Abilities.PROTOSYNTHESIS, WeatherType.SUNNY, WeatherType.HARSH_SUN); + case BattlerTagType.QUARK_DRIVE: + return new TerrainHighestStatBoostTag(tagType, Abilities.QUARK_DRIVE, TerrainType.ELECTRIC); + case BattlerTagType.FLYING: + case BattlerTagType.UNDERGROUND: + case BattlerTagType.UNDERWATER: + case BattlerTagType.HIDDEN: + return new SemiInvulnerableTag(tagType, turnCount, sourceMove); + case BattlerTagType.FIRE_BOOST: + return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false); + case BattlerTagType.CRIT_BOOST: + return new CritBoostTag(tagType, sourceMove); + case BattlerTagType.DRAGON_CHEER: + return new DragonCheerTag(); + case BattlerTagType.ALWAYS_CRIT: + case BattlerTagType.IGNORE_ACCURACY: + return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove); + case BattlerTagType.ALWAYS_GET_HIT: + case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: + return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove); + case BattlerTagType.BYPASS_SLEEP: + return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove); + case BattlerTagType.IGNORE_FLYING: + return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); + case BattlerTagType.ROOSTED: + return new RoostedTag(); + case BattlerTagType.BURNED_UP: + return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); + case BattlerTagType.DOUBLE_SHOCKED: + return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); + case BattlerTagType.SALT_CURED: + return new SaltCuredTag(sourceId); + case BattlerTagType.CURSED: + return new CursedTag(sourceId); + case BattlerTagType.CHARGED: + return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true); + case BattlerTagType.FLOATING: + return new FloatingTag(tagType, sourceMove); + case BattlerTagType.MINIMIZED: + return new MinimizeTag(); + case BattlerTagType.DESTINY_BOND: + return new DestinyBondTag(sourceMove, sourceId); + case BattlerTagType.ICE_FACE: + return new IceFaceBlockDamageTag(tagType); + case BattlerTagType.DISGUISE: + return new FormBlockDamageTag(tagType); + case BattlerTagType.STOCKPILING: + return new StockpilingTag(sourceMove); + case BattlerTagType.OCTOLOCK: + return new OctolockTag(sourceId); + case BattlerTagType.DISABLED: + return new DisabledTag(sourceId); + case BattlerTagType.IGNORE_GHOST: + return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]); + case BattlerTagType.IGNORE_DARK: + return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]); + case BattlerTagType.GULP_MISSILE_ARROKUDA: + case BattlerTagType.GULP_MISSILE_PIKACHU: + return new GulpMissileTag(tagType, sourceMove); + case BattlerTagType.TAR_SHOT: + return new TarShotTag(); + case BattlerTagType.ELECTRIFIED: + return new ElectrifiedTag(); + case BattlerTagType.THROAT_CHOPPED: + return new ThroatChoppedTag(); + case BattlerTagType.GORILLA_TACTICS: + return new GorillaTacticsTag(); + case BattlerTagType.SUBSTITUTE: + return new SubstituteTag(sourceMove, sourceId); + case BattlerTagType.AUTOTOMIZED: + return new AutotomizedTag(); + case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON: + return new MysteryEncounterPostSummonTag(); + case BattlerTagType.HEAL_BLOCK: + return new HealBlockTag(turnCount, sourceMove); + case BattlerTagType.TORMENT: + return new TormentTag(sourceId); + case BattlerTagType.TAUNT: + return new TauntTag(); + case BattlerTagType.IMPRISON: + return new ImprisonTag(sourceId); + case BattlerTagType.SYRUP_BOMB: + return new SyrupBombTag(sourceId); + case BattlerTagType.TELEKINESIS: + return new TelekinesisTag(sourceMove); + case BattlerTagType.POWER_TRICK: + return new PowerTrickTag(sourceMove, sourceId); + case BattlerTagType.NONE: + default: + return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); } } diff --git a/src/data/berry.ts b/src/data/berry.ts index 01325ee39dd..7243c4c1b2e 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -22,42 +22,42 @@ export type BerryPredicate = (pokemon: Pokemon) => boolean; export function getBerryPredicate(berryType: BerryType): BerryPredicate { switch (berryType) { - case BerryType.SITRUS: - return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.5; - case BerryType.LUM: - return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED); - case BerryType.ENIGMA: - return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length; - case BerryType.LIECHI: - case BerryType.GANLON: - case BerryType.PETAYA: - case BerryType.APICOT: - case BerryType.SALAC: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth - const stat: BattleStat = berryType - BerryType.ENIGMA; - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6; - }; - case BerryType.LANSAT: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); - }; - case BerryType.STARF: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return pokemon.getHpRatio() < 0.25; - }; - case BerryType.LEPPA: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return !!pokemon.getMoveset().find(m => !m?.getPpRatio()); - }; + case BerryType.SITRUS: + return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.5; + case BerryType.LUM: + return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED); + case BerryType.ENIGMA: + return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length; + case BerryType.LIECHI: + case BerryType.GANLON: + case BerryType.PETAYA: + case BerryType.APICOT: + case BerryType.SALAC: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth + const stat: BattleStat = berryType - BerryType.ENIGMA; + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6; + }; + case BerryType.LANSAT: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); + }; + case BerryType.STARF: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return pokemon.getHpRatio() < 0.25; + }; + case BerryType.LEPPA: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return !!pokemon.getMoveset().find(m => !m?.getPpRatio()); + }; } } @@ -65,70 +65,70 @@ export type BerryEffectFunc = (pokemon: Pokemon) => void; export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { switch (berryType) { - case BerryType.SITRUS: - case BerryType.ENIGMA: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4)); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed); - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), - hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true)); - }; - case BerryType.LUM: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - if (pokemon.status) { - pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); - } - pokemon.resetStatus(true, true); - pokemon.updateInfo(); - }; - case BerryType.LIECHI: - case BerryType.GANLON: - case BerryType.PETAYA: - case BerryType.APICOT: - case BerryType.SALAC: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth - const stat: BattleStat = berryType - BerryType.ENIGMA; - const statStages = new Utils.NumberHolder(1); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); - }; - case BerryType.LANSAT: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - pokemon.addTag(BattlerTagType.CRIT_BOOST); - }; - case BerryType.STARF: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK); - const stages = new Utils.NumberHolder(2); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value)); - }; - case BerryType.LEPPA: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct? - if (ppRestoreMove !== undefined) { + case BerryType.SITRUS: + case BerryType.ENIGMA: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4)); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed); + pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), + hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true)); + }; + case BerryType.LUM: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + if (pokemon.status) { + pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + } + pokemon.resetStatus(true, true); + pokemon.updateInfo(); + }; + case BerryType.LIECHI: + case BerryType.GANLON: + case BerryType.PETAYA: + case BerryType.APICOT: + case BerryType.SALAC: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth + const stat: BattleStat = berryType - BerryType.ENIGMA; + const statStages = new Utils.NumberHolder(1); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); + }; + case BerryType.LANSAT: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + pokemon.addTag(BattlerTagType.CRIT_BOOST); + }; + case BerryType.STARF: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK); + const stages = new Utils.NumberHolder(2); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value)); + }; + case BerryType.LEPPA: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct? + if (ppRestoreMove !== undefined) { ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0); pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) })); - } - }; + } + }; } } diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 7ecdfbf36e1..a64a90e5d14 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -448,21 +448,21 @@ export class SingleGenerationChallenge extends Challenge { applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean { let trainerTypes: TrainerType[] = []; switch (waveIndex) { - case 182: - trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; - break; - case 184: - trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ]; - break; - case 186: - trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ]; - break; - case 188: - trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ]; - break; - case 190: - 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, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; - break; + case 182: + trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; + break; + case 184: + trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ]; + break; + case 186: + trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ]; + break; + case 188: + trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ]; + break; + case 190: + 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, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; + break; } if (trainerTypes.length === 0) { return false; @@ -891,45 +891,45 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType gameMode.challenges.forEach(c => { if (c.value !== 0) { switch (challengeType) { - case ChallengeType.STARTER_CHOICE: - ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]); - break; - case ChallengeType.STARTER_POINTS: - ret ||= c.applyStarterPoints(args[0]); - break; - case ChallengeType.STARTER_COST: - ret ||= c.applyStarterCost(args[0], args[1]); - break; - case ChallengeType.STARTER_MODIFY: - ret ||= c.applyStarterModify(args[0]); - break; - case ChallengeType.POKEMON_IN_BATTLE: - ret ||= c.applyPokemonInBattle(args[0], args[1]); - break; - case ChallengeType.FIXED_BATTLES: - ret ||= c.applyFixedBattle(args[0], args[1]); - break; - case ChallengeType.TYPE_EFFECTIVENESS: - ret ||= c.applyTypeEffectiveness(args[0]); - break; - case ChallengeType.AI_LEVEL: - ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]); - break; - case ChallengeType.AI_MOVE_SLOTS: - ret ||= c.applyMoveSlot(args[0], args[1]); - break; - case ChallengeType.PASSIVE_ACCESS: - ret ||= c.applyPassiveAccess(args[0], args[1]); - break; - case ChallengeType.GAME_MODE_MODIFY: - ret ||= c.applyGameModeModify(gameMode); - break; - case ChallengeType.MOVE_ACCESS: - ret ||= c.applyMoveAccessLevel(args[0], args[1], args[2], args[3]); - break; - case ChallengeType.MOVE_WEIGHT: - ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]); - break; + case ChallengeType.STARTER_CHOICE: + ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]); + break; + case ChallengeType.STARTER_POINTS: + ret ||= c.applyStarterPoints(args[0]); + break; + case ChallengeType.STARTER_COST: + ret ||= c.applyStarterCost(args[0], args[1]); + break; + case ChallengeType.STARTER_MODIFY: + ret ||= c.applyStarterModify(args[0]); + break; + case ChallengeType.POKEMON_IN_BATTLE: + ret ||= c.applyPokemonInBattle(args[0], args[1]); + break; + case ChallengeType.FIXED_BATTLES: + ret ||= c.applyFixedBattle(args[0], args[1]); + break; + case ChallengeType.TYPE_EFFECTIVENESS: + ret ||= c.applyTypeEffectiveness(args[0]); + break; + case ChallengeType.AI_LEVEL: + ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]); + break; + case ChallengeType.AI_MOVE_SLOTS: + ret ||= c.applyMoveSlot(args[0], args[1]); + break; + case ChallengeType.PASSIVE_ACCESS: + ret ||= c.applyPassiveAccess(args[0], args[1]); + break; + case ChallengeType.GAME_MODE_MODIFY: + ret ||= c.applyGameModeModify(gameMode); + break; + case ChallengeType.MOVE_ACCESS: + ret ||= c.applyMoveAccessLevel(args[0], args[1], args[2], args[3]); + break; + case ChallengeType.MOVE_WEIGHT: + ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]); + break; } } }); @@ -943,18 +943,18 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType */ export function copyChallenge(source: Challenge | any): Challenge { switch (source.id) { - case Challenges.SINGLE_GENERATION: - return SingleGenerationChallenge.loadChallenge(source); - case Challenges.SINGLE_TYPE: - return SingleTypeChallenge.loadChallenge(source); - case Challenges.LOWER_MAX_STARTER_COST: - return LowerStarterMaxCostChallenge.loadChallenge(source); - case Challenges.LOWER_STARTER_POINTS: - return LowerStarterPointsChallenge.loadChallenge(source); - case Challenges.FRESH_START: - return FreshStartChallenge.loadChallenge(source); - case Challenges.INVERSE_BATTLE: - return InverseBattleChallenge.loadChallenge(source); + case Challenges.SINGLE_GENERATION: + return SingleGenerationChallenge.loadChallenge(source); + case Challenges.SINGLE_TYPE: + return SingleTypeChallenge.loadChallenge(source); + case Challenges.LOWER_MAX_STARTER_COST: + return LowerStarterMaxCostChallenge.loadChallenge(source); + case Challenges.LOWER_STARTER_POINTS: + return LowerStarterPointsChallenge.loadChallenge(source); + case Challenges.FRESH_START: + return FreshStartChallenge.loadChallenge(source); + case Challenges.INVERSE_BATTLE: + return InverseBattleChallenge.loadChallenge(source); } throw new Error("Unknown challenge copied"); } diff --git a/src/data/mystery-encounters/mystery-encounter-pokemon-data.ts b/src/data/custom-pokemon-data.ts similarity index 68% rename from src/data/mystery-encounters/mystery-encounter-pokemon-data.ts rename to src/data/custom-pokemon-data.ts index fc6ce313d41..2e94123fc84 100644 --- a/src/data/mystery-encounters/mystery-encounter-pokemon-data.ts +++ b/src/data/custom-pokemon-data.ts @@ -1,18 +1,20 @@ import { Abilities } from "#enums/abilities"; import { Type } from "#app/data/type"; import { isNullOrUndefined } from "#app/utils"; +import { Nature } from "#enums/nature"; /** * Data that can customize a Pokemon in non-standard ways from its Species - * Currently only used by Mystery Encounters, may need to be renamed if it becomes more widely used + * Currently only used by Mystery Encounters and Mints. */ -export class MysteryEncounterPokemonData { +export class CustomPokemonData { public spriteScale: number; public ability: Abilities | -1; public passive: Abilities | -1; + public nature: Nature | -1; public types: Type[]; - constructor(data?: MysteryEncounterPokemonData | Partial) { + constructor(data?: CustomPokemonData | Partial) { if (!isNullOrUndefined(data)) { Object.assign(this, data); } @@ -20,6 +22,7 @@ export class MysteryEncounterPokemonData { this.spriteScale = this.spriteScale ?? -1; this.ability = this.ability ?? -1; this.passive = this.passive ?? -1; + this.nature = this.nature ?? -1; this.types = this.types ?? []; } } diff --git a/src/data/egg.ts b/src/data/egg.ts index 5fffe4fcece..0f88c53b748 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -11,6 +11,7 @@ import { EggTier } from "#enums/egg-type"; import { Species } from "#enums/species"; import { EggSourceType } from "#enums/egg-source-types"; import { MANAPHY_EGG_MANAPHY_RATE, SAME_SPECIES_EGG_HA_RATE, GACHA_EGG_HA_RATE, GACHA_DEFAULT_RARE_EGGMOVE_RATE, SAME_SPECIES_EGG_RARE_EGGMOVE_RATE, GACHA_MOVE_UP_RARE_EGGMOVE_RATE, GACHA_DEFAULT_SHINY_RATE, GACHA_SHINY_UP_SHINY_RATE, SAME_SPECIES_EGG_SHINY_RATE, EGG_PITY_LEGENDARY_THRESHOLD, EGG_PITY_EPIC_THRESHOLD, EGG_PITY_RARE_THRESHOLD, SHINY_VARIANT_CHANCE, SHINY_EPIC_CHANCE, GACHA_DEFAULT_COMMON_EGG_THRESHOLD, GACHA_DEFAULT_RARE_EGG_THRESHOLD, GACHA_DEFAULT_EPIC_EGG_THRESHOLD, GACHA_LEGENDARY_UP_THRESHOLD_OFFSET, HATCH_WAVES_MANAPHY_EGG, HATCH_WAVES_COMMON_EGG, HATCH_WAVES_RARE_EGG, HATCH_WAVES_EPIC_EGG, HATCH_WAVES_LEGENDARY_EGG } from "#app/data/balance/rates"; +import { speciesEggTiers } from "#app/data/balance/species-egg-tiers"; export const EGG_SEED = 1073741824; @@ -160,7 +161,7 @@ export class Egg { // Override egg tier and hatchwaves if species was given if (eggOptions?.species) { - this._tier = this.getEggTierFromSpeciesStarterValue(); + this._tier = this.getEggTier(); this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves(); } // If species has no variant, set variantTier to common. This needs to @@ -261,14 +262,14 @@ export class Egg { return "Manaphy"; } switch (this.tier) { - case EggTier.GREAT: - return i18next.t("egg:greatTier"); - case EggTier.ULTRA: - return i18next.t("egg:ultraTier"); - case EggTier.MASTER: - return i18next.t("egg:masterTier"); - default: - return i18next.t("egg:defaultTier"); + case EggTier.RARE: + return i18next.t("egg:greatTier"); + case EggTier.EPIC: + return i18next.t("egg:ultraTier"); + case EggTier.LEGENDARY: + return i18next.t("egg:masterTier"); + default: + return i18next.t("egg:defaultTier"); } } @@ -287,19 +288,19 @@ export class Egg { public getEggTypeDescriptor(scene: BattleScene): string { switch (this.sourceType) { - case EggSourceType.SAME_SPECIES_EGG: - return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() }); - case EggSourceType.GACHA_LEGENDARY: - return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`; - case EggSourceType.GACHA_SHINY: - return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny"); - case EggSourceType.GACHA_MOVE: - return this._eggDescriptor ?? i18next.t("egg:gachaTypeMove"); - case EggSourceType.EVENT: - return this._eggDescriptor ?? i18next.t("egg:eventType"); - default: - console.warn("getEggTypeDescriptor case not defined. Returning default empty string"); - return ""; + case EggSourceType.SAME_SPECIES_EGG: + return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() }); + case EggSourceType.GACHA_LEGENDARY: + return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`; + case EggSourceType.GACHA_SHINY: + return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny"); + case EggSourceType.GACHA_MOVE: + return this._eggDescriptor ?? i18next.t("egg:gachaTypeMove"); + case EggSourceType.EVENT: + return this._eggDescriptor ?? i18next.t("egg:eventType"); + default: + console.warn("getEggTypeDescriptor case not defined. Returning default empty string"); + return ""; } } @@ -314,14 +315,14 @@ export class Egg { private rollEggMoveIndex() { let baseChance = GACHA_DEFAULT_RARE_EGGMOVE_RATE; switch (this._sourceType) { - case EggSourceType.SAME_SPECIES_EGG: - baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE; - break; - case EggSourceType.GACHA_MOVE: - baseChance = GACHA_MOVE_UP_RARE_EGGMOVE_RATE; - break; - default: - break; + case EggSourceType.SAME_SPECIES_EGG: + baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE; + break; + case EggSourceType.GACHA_MOVE: + baseChance = GACHA_MOVE_UP_RARE_EGGMOVE_RATE; + break; + default: + break; } const tierMultiplier = this.isManaphyEgg() ? 2 : Math.pow(2, 3 - this.tier); @@ -334,12 +335,12 @@ export class Egg { } switch (eggTier ?? this._tier) { - case EggTier.COMMON: - return HATCH_WAVES_COMMON_EGG; - case EggTier.GREAT: - return HATCH_WAVES_RARE_EGG; - case EggTier.ULTRA: - return HATCH_WAVES_EPIC_EGG; + case EggTier.COMMON: + return HATCH_WAVES_COMMON_EGG; + case EggTier.RARE: + return HATCH_WAVES_RARE_EGG; + case EggTier.EPIC: + return HATCH_WAVES_EPIC_EGG; } return HATCH_WAVES_LEGENDARY_EGG; } @@ -347,7 +348,7 @@ export class Egg { private rollEggTier(): EggTier { const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; const tierValue = Utils.randInt(256); - return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.GREAT : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER; + 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(scene: BattleScene): Species | null { @@ -367,7 +368,7 @@ export class Egg { */ const rand = (Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1); return rand ? Species.PHIONE : Species.MANAPHY; - } else if (this.tier === EggTier.MASTER + } else if (this.tier === EggTier.LEGENDARY && this._sourceType === EggSourceType.GACHA_LEGENDARY) { if (!Utils.randSeedInt(2)) { return getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp); @@ -378,28 +379,28 @@ export class Egg { let maxStarterValue: integer; switch (this.tier) { - case EggTier.GREAT: - minStarterValue = 4; - maxStarterValue = 5; - break; - case EggTier.ULTRA: - minStarterValue = 6; - maxStarterValue = 7; - break; - case EggTier.MASTER: - minStarterValue = 8; - maxStarterValue = 9; - break; - default: - minStarterValue = 1; - maxStarterValue = 3; - break; + case EggTier.RARE: + minStarterValue = 4; + maxStarterValue = 5; + break; + case EggTier.EPIC: + minStarterValue = 6; + maxStarterValue = 7; + break; + case EggTier.LEGENDARY: + minStarterValue = 8; + maxStarterValue = 9; + break; + default: + minStarterValue = 1; + maxStarterValue = 3; + break; } const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ]; - let speciesPool = Object.keys(speciesStarterCosts) - .filter(s => speciesStarterCosts[s] >= minStarterValue && speciesStarterCosts[s] <= maxStarterValue) + let speciesPool = Object.keys(speciesEggTiers) + .filter(s => speciesEggTiers[s] === this.tier) .map(s => parseInt(s) as Species) .filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1); @@ -430,7 +431,9 @@ export class Egg { let totalWeight = 0; const speciesWeights : number[] = []; for (const speciesId of speciesPool) { - let weight = Math.floor((((maxStarterValue - speciesStarterCosts[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); + // Accounts for species that have starter costs outside of the normal range for their EggTier + const speciesCostClamped = Phaser.Math.Clamp(speciesStarterCosts[speciesId], minStarterValue, maxStarterValue); + let weight = Math.floor((((maxStarterValue - speciesCostClamped) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); const species = getPokemonSpecies(speciesId); if (species.isRegional()) { weight = Math.floor(weight / 2); @@ -466,14 +469,14 @@ export class Egg { private rollShiny(): boolean { let shinyChance = GACHA_DEFAULT_SHINY_RATE; switch (this._sourceType) { - case EggSourceType.GACHA_SHINY: - shinyChance = GACHA_SHINY_UP_SHINY_RATE; - break; - case EggSourceType.SAME_SPECIES_EGG: - shinyChance = SAME_SPECIES_EGG_SHINY_RATE; - break; - default: - break; + case EggSourceType.GACHA_SHINY: + shinyChance = GACHA_SHINY_UP_SHINY_RATE; + break; + case EggSourceType.SAME_SPECIES_EGG: + shinyChance = SAME_SPECIES_EGG_SHINY_RATE; + break; + default: + break; } return !Utils.randSeedInt(shinyChance); @@ -498,16 +501,16 @@ export class Egg { private checkForPityTierOverrides(scene: BattleScene): void { const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; - scene.gameData.eggPity[EggTier.GREAT] += 1; - scene.gameData.eggPity[EggTier.ULTRA] += 1; - scene.gameData.eggPity[EggTier.MASTER] += 1 + tierValueOffset; + scene.gameData.eggPity[EggTier.RARE] += 1; + scene.gameData.eggPity[EggTier.EPIC] += 1; + scene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset; // These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered. - if (scene.gameData.eggPity[EggTier.MASTER] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) { - this._tier = EggTier.MASTER; - } else if (scene.gameData.eggPity[EggTier.ULTRA] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) { - this._tier = EggTier.ULTRA; - } else if (scene.gameData.eggPity[EggTier.GREAT] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) { - this._tier = EggTier.GREAT; + if (scene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) { + this._tier = EggTier.LEGENDARY; + } else if (scene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) { + this._tier = EggTier.EPIC; + } else if (scene.gameData.eggPity[EggTier.RARE] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) { + this._tier = EggTier.RARE; } scene.gameData.eggPity[this._tier] = 0; } @@ -516,38 +519,24 @@ export class Egg { scene.gameData.gameStats.eggsPulled++; if (this.isManaphyEgg()) { scene.gameData.gameStats.manaphyEggsPulled++; - this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.ULTRA); + this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.EPIC); return; } switch (this.tier) { - case EggTier.GREAT: - scene.gameData.gameStats.rareEggsPulled++; - break; - case EggTier.ULTRA: - scene.gameData.gameStats.epicEggsPulled++; - break; - case EggTier.MASTER: - scene.gameData.gameStats.legendaryEggsPulled++; - break; + case EggTier.RARE: + scene.gameData.gameStats.rareEggsPulled++; + break; + case EggTier.EPIC: + scene.gameData.gameStats.epicEggsPulled++; + break; + case EggTier.LEGENDARY: + scene.gameData.gameStats.legendaryEggsPulled++; + break; } } - private getEggTierFromSpeciesStarterValue(): EggTier { - const speciesStartValue = speciesStarterCosts[this.species]; - if (speciesStartValue >= 1 && speciesStartValue <= 3) { - return EggTier.COMMON; - } - if (speciesStartValue >= 4 && speciesStartValue <= 5) { - return EggTier.GREAT; - } - if (speciesStartValue >= 6 && speciesStartValue <= 7) { - return EggTier.ULTRA; - } - if (speciesStartValue >= 8) { - return EggTier.MASTER; - } - - return EggTier.COMMON; + private getEggTier(): EggTier { + return speciesEggTiers[this.species]; } //// @@ -556,8 +545,8 @@ export class Egg { } export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species { - const legendarySpecies = Object.entries(speciesStarterCosts) - .filter(s => s[1] >= 8 && s[1] <= 9) + const legendarySpecies = Object.entries(speciesEggTiers) + .filter(s => s[1] === EggTier.LEGENDARY) .map(s => parseInt(s[0])) .filter(s => getPokemonSpecies(s).isObtainable()); @@ -579,17 +568,9 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta /** * Check for a given species EggTier Value - * @param species - 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 */ export function getEggTierForSpecies(pokemonSpecies :PokemonSpecies): EggTier { - const speciesBaseValue = speciesStarterCosts[pokemonSpecies.getRootSpeciesId()]; - if (speciesBaseValue <= 3) { - return EggTier.COMMON; - } else if (speciesBaseValue <= 5) { - return EggTier.GREAT; - } else if (speciesBaseValue <= 7) { - return EggTier.ULTRA; - } - return EggTier.MASTER; + return speciesEggTiers[pokemonSpecies.getRootSpeciesId()]; } diff --git a/src/data/exp.ts b/src/data/exp.ts index 3b332eb7cf2..c03abddadfc 100644 --- a/src/data/exp.ts +++ b/src/data/exp.ts @@ -28,24 +28,24 @@ export function getLevelTotalExp(level: integer, growthRate: GrowthRate): intege let ret: integer; switch (growthRate) { - case GrowthRate.ERRATIC: - ret = (Math.pow(level, 4) + (Math.pow(level, 3) * 2000)) / 3500; - break; - case GrowthRate.FAST: - ret = Math.pow(level, 3) * 4 / 5; - break; - case GrowthRate.MEDIUM_FAST: - ret = Math.pow(level, 3); - break; - case GrowthRate.MEDIUM_SLOW: - ret = (Math.pow(level, 3) * 6 / 5) - (15 * Math.pow(level, 2)) + (100 * level) - 140; - break; - case GrowthRate.SLOW: - ret = Math.pow(level, 3) * 5 / 4; - break; - case GrowthRate.FLUCTUATING: - ret = (Math.pow(level, 3) * ((level / 2) + 8)) * 4 / (100 + level); - break; + case GrowthRate.ERRATIC: + ret = (Math.pow(level, 4) + (Math.pow(level, 3) * 2000)) / 3500; + break; + case GrowthRate.FAST: + ret = Math.pow(level, 3) * 4 / 5; + break; + case GrowthRate.MEDIUM_FAST: + ret = Math.pow(level, 3); + break; + case GrowthRate.MEDIUM_SLOW: + ret = (Math.pow(level, 3) * 6 / 5) - (15 * Math.pow(level, 2)) + (100 * level) - 140; + break; + case GrowthRate.SLOW: + ret = Math.pow(level, 3) * 5 / 4; + break; + case GrowthRate.FLUCTUATING: + ret = (Math.pow(level, 3) * ((level / 2) + 8)) * 4 / (100 + level); + break; } if (growthRate !== GrowthRate.MEDIUM_FAST) { @@ -61,17 +61,17 @@ export function getLevelRelExp(level: integer, growthRate: GrowthRate): number { export function getGrowthRateColor(growthRate: GrowthRate, shadow?: boolean) { switch (growthRate) { - case GrowthRate.ERRATIC: - return !shadow ? "#f85888" : "#906060"; - case GrowthRate.FAST: - return !shadow ? "#f8d030" : "#b8a038"; - case GrowthRate.MEDIUM_FAST: - return !shadow ? "#78c850" : "#588040"; - case GrowthRate.MEDIUM_SLOW: - return !shadow ? "#6890f0" : "#807870"; - case GrowthRate.SLOW: - return !shadow ? "#f08030" : "#c03028"; - case GrowthRate.FLUCTUATING: - return !shadow ? "#a040a0" : "#483850"; + case GrowthRate.ERRATIC: + return !shadow ? "#f85888" : "#906060"; + case GrowthRate.FAST: + return !shadow ? "#f8d030" : "#b8a038"; + case GrowthRate.MEDIUM_FAST: + return !shadow ? "#78c850" : "#588040"; + case GrowthRate.MEDIUM_SLOW: + return !shadow ? "#6890f0" : "#807870"; + case GrowthRate.SLOW: + return !shadow ? "#f08030" : "#c03028"; + case GrowthRate.FLUCTUATING: + return !shadow ? "#a040a0" : "#483850"; } } diff --git a/src/data/gender.ts b/src/data/gender.ts index 0d4b76d8bd1..dae7723dd85 100644 --- a/src/data/gender.ts +++ b/src/data/gender.ts @@ -6,20 +6,20 @@ export enum Gender { export function getGenderSymbol(gender: Gender) { switch (gender) { - case Gender.MALE: - return "♂"; - case Gender.FEMALE: - return "♀"; + case Gender.MALE: + return "♂"; + case Gender.FEMALE: + return "♀"; } return ""; } export function getGenderColor(gender: Gender, shadow?: boolean) { switch (gender) { - case Gender.MALE: - return shadow ? "#006090" : "#40c8f8"; - case Gender.FEMALE: - return shadow ? "#984038" : "#f89890"; + case Gender.MALE: + return shadow ? "#006090" : "#40c8f8"; + case Gender.FEMALE: + return shadow ? "#984038" : "#f89890"; } return "#ffffff"; } diff --git a/src/data/move.ts b/src/data/move.ts index f795d265336..309a2d3a7eb 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -274,16 +274,16 @@ export default class Move implements Localizable { */ isMultiTarget(): boolean { switch (this.moveTarget) { - case MoveTarget.ALL_OTHERS: - case MoveTarget.ALL_NEAR_OTHERS: - case MoveTarget.ALL_NEAR_ENEMIES: - case MoveTarget.ALL_ENEMIES: - case MoveTarget.USER_AND_ALLIES: - case MoveTarget.ALL: - case MoveTarget.USER_SIDE: - case MoveTarget.ENEMY_SIDE: - case MoveTarget.BOTH_SIDES: - return true; + case MoveTarget.ALL_OTHERS: + case MoveTarget.ALL_NEAR_OTHERS: + case MoveTarget.ALL_NEAR_ENEMIES: + case MoveTarget.ALL_ENEMIES: + case MoveTarget.USER_AND_ALLIES: + case MoveTarget.ALL: + case MoveTarget.USER_SIDE: + case MoveTarget.ENEMY_SIDE: + case MoveTarget.BOTH_SIDES: + return true; } return false; } @@ -295,13 +295,13 @@ export default class Move implements Localizable { isAllyTarget(): boolean { switch (this.moveTarget) { - case MoveTarget.USER: - case MoveTarget.NEAR_ALLY: - case MoveTarget.ALLY: - case MoveTarget.USER_OR_NEAR_ALLY: - case MoveTarget.USER_AND_ALLIES: - case MoveTarget.USER_SIDE: - return true; + case MoveTarget.USER: + case MoveTarget.NEAR_ALLY: + case MoveTarget.ALLY: + case MoveTarget.USER_OR_NEAR_ALLY: + case MoveTarget.USER_AND_ALLIES: + case MoveTarget.USER_SIDE: + return true; } return false; } @@ -320,16 +320,16 @@ export default class Move implements Localizable { } switch (type) { - case Type.GRASS: - if (this.hasFlag(MoveFlags.POWDER_MOVE)) { - return true; - } - break; - case Type.DARK: - if (user.hasAbility(Abilities.PRANKSTER) && this.category === MoveCategory.STATUS && (user.isPlayer() !== target.isPlayer())) { - return true; - } - break; + case Type.GRASS: + if (this.hasFlag(MoveFlags.POWDER_MOVE)) { + return true; + } + break; + case Type.DARK: + if (user.hasAbility(Abilities.PRANKSTER) && this.category === MoveCategory.STATUS && (user.isPlayer() !== target.isPlayer())) { + return true; + } + break; } return false; } @@ -365,6 +365,14 @@ export default class Move implements Localizable { return this; } + /** + * Internal dev flag for documenting edge cases. When using this, please document the known edge case. + * @returns the called object {@linkcode Move} + */ + edgeCase(): this { + return this; + } + /** * Marks the move as "partial": appends texts to the move name * @returns the called object {@linkcode Move} @@ -608,26 +616,26 @@ export default class Move implements Localizable { checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon | null): boolean { // special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact switch (flag) { - case MoveFlags.MAKES_CONTACT: - if (user.hasAbilityWithAttr(IgnoreContactAbAttr) || this.hitsSubstitute(user, target)) { - return false; - } - break; - case MoveFlags.IGNORE_ABILITIES: - if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { - const abilityEffectsIgnored = new Utils.BooleanHolder(false); - applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); - if (abilityEffectsIgnored.value) { + case MoveFlags.MAKES_CONTACT: + if (user.hasAbilityWithAttr(IgnoreContactAbAttr) || this.hitsSubstitute(user, target)) { + return false; + } + break; + case MoveFlags.IGNORE_ABILITIES: + if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { + const abilityEffectsIgnored = new Utils.BooleanHolder(false); + applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); + if (abilityEffectsIgnored.value) { + return true; + } + } + break; + case MoveFlags.IGNORE_PROTECT: + if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) + && this.checkFlag(MoveFlags.MAKES_CONTACT, user, null)) { return true; } - } - break; - case MoveFlags.IGNORE_PROTECT: - if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) - && this.checkFlag(MoveFlags.MAKES_CONTACT, user, null)) { - return true; - } - break; + break; } return !!(this.flags & flag); @@ -800,7 +808,7 @@ export default class Move implements Localizable { source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power); if (!this.hasAttr(TypelessAttr)) { - source.scene.arena.applyTags(WeakenMoveTypeTag, this.type, power); + source.scene.arena.applyTags(WeakenMoveTypeTag, simulated, this.type, power); source.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power); } @@ -970,13 +978,16 @@ export class MoveEffectAttr extends MoveAttr { public lastHitOnly: boolean; /** Should this effect only apply on the first target hit? */ public firstTargetOnly: boolean; + /** Overrides the secondary effect chance for this attr if set. */ + public effectChanceOverride?: number; - constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false, lastHitOnly: boolean = false, firstTargetOnly: boolean = false) { + constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false, lastHitOnly: boolean = false, firstTargetOnly: boolean = false, effectChanceOverride?: number) { super(selfTarget); - this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY; + this.trigger = trigger ?? MoveEffectTrigger.POST_APPLY; this.firstHitOnly = firstHitOnly; this.lastHitOnly = lastHitOnly; this.firstTargetOnly = firstTargetOnly; + this.effectChanceOverride = effectChanceOverride; } /** @@ -1001,21 +1012,21 @@ export class MoveEffectAttr extends MoveAttr { /** * Gets the used move's additional effect chance. - * If user's ability has MoveEffectChanceMultiplierAbAttr or IgnoreMoveEffectsAbAttr modifies the base chance. + * Chance is modified by {@linkcode MoveEffectChanceMultiplierAbAttr} and {@linkcode IgnoreMoveEffectsAbAttr}. * @param user {@linkcode Pokemon} using this move - * @param target {@linkcode Pokemon} target of this move + * @param target {@linkcode Pokemon | Target} of this move * @param move {@linkcode Move} being used - * @param selfEffect {@linkcode Boolean} if move targets user. - * @returns Move chance value. + * @param selfEffect `true` if move targets user. + * @returns Move effect chance value. */ getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): integer { - const moveChance = new Utils.NumberHolder(move.chance); + const moveChance = new Utils.NumberHolder(this.effectChanceOverride ?? move.chance); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, moveChance, move, target, selfEffect, showAbility); - if (!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) { + if ((!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) && !move.hasAttr(SecretPowerAttr)) { const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - user.scene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, moveChance); + user.scene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, false, moveChance); } if (!selfEffect) { @@ -1734,17 +1745,17 @@ export abstract class WeatherHealAttr extends HealAttr { export class PlantHealAttr extends WeatherHealAttr { getWeatherHealRatio(weatherType: WeatherType): number { switch (weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - return 2 / 3; - case WeatherType.RAIN: - case WeatherType.SANDSTORM: - case WeatherType.HAIL: - case WeatherType.SNOW: - case WeatherType.HEAVY_RAIN: - return 0.25; - default: - return 0.5; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + return 2 / 3; + case WeatherType.RAIN: + case WeatherType.SANDSTORM: + case WeatherType.HAIL: + case WeatherType.SNOW: + case WeatherType.HEAVY_RAIN: + return 0.25; + default: + return 0.5; } } } @@ -1752,10 +1763,10 @@ export class PlantHealAttr extends WeatherHealAttr { export class SandHealAttr extends WeatherHealAttr { getWeatherHealRatio(weatherType: WeatherType): number { switch (weatherType) { - case WeatherType.SANDSTORM: - return 2 / 3; - default: - return 0.5; + case WeatherType.SANDSTORM: + return 2 / 3; + default: + return 0.5; } } } @@ -1935,12 +1946,21 @@ export class IncrementMovePriorityAttr extends MoveAttr { * @see {@linkcode apply} */ export class MultiHitAttr extends MoveAttr { + /** This move's intrinsic multi-hit type. It should never be modified. */ + private readonly intrinsicMultiHitType: MultiHitType; + /** This move's current multi-hit type. It may be temporarily modified by abilities (e.g., Battle Bond). */ private multiHitType: MultiHitType; constructor(multiHitType?: MultiHitType) { super(); - this.multiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5; + this.intrinsicMultiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5; + this.multiHitType = this.intrinsicMultiHitType; + } + + // Currently used by `battle_bond.test.ts` + getMultiHitType(): MultiHitType { + return this.multiHitType; } /** @@ -1954,7 +1974,7 @@ export class MultiHitAttr extends MoveAttr { * @returns True */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const hitType = new Utils.NumberHolder(this.multiHitType); + const hitType = new Utils.NumberHolder(this.intrinsicMultiHitType); applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType); this.multiHitType = hitType.value; @@ -1976,33 +1996,33 @@ export class MultiHitAttr extends MoveAttr { */ getHitCount(user: Pokemon, target: Pokemon): integer { switch (this.multiHitType) { - case MultiHitType._2_TO_5: - { - const rand = user.randSeedInt(16); - const hitValue = new Utils.IntegerHolder(rand); - applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); - if (hitValue.value >= 10) { - return 2; - } else if (hitValue.value >= 4) { - return 3; - } else if (hitValue.value >= 2) { - return 4; - } else { - return 5; + case MultiHitType._2_TO_5: + { + const rand = user.randSeedInt(16); + const hitValue = new Utils.IntegerHolder(rand); + applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); + if (hitValue.value >= 10) { + return 2; + } else if (hitValue.value >= 4) { + return 3; + } else if (hitValue.value >= 2) { + return 4; + } else { + return 5; + } } - } - case MultiHitType._2: - return 2; - case MultiHitType._3: - return 3; - case MultiHitType._10: - return 10; - case MultiHitType.BEAT_UP: - const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); - // No status means the ally pokemon can contribute to Beat Up - return party.reduce((total, pokemon) => { - return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); - }, 0); + case MultiHitType._2: + return 2; + case MultiHitType._3: + return 3; + case MultiHitType._10: + return 10; + case MultiHitType.BEAT_UP: + const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); + // No status means the ally pokemon can contribute to Beat Up + return party.reduce((total, pokemon) => { + return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); + }, 0); } } } @@ -2752,14 +2772,17 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { /** * Attribute used for moves that change stat stages - * @param stats {@linkcode BattleStat} array of stats to be changed - * @param stages stages by which to change the stats, from -6 to 6 - * @param selfTarget whether the changes are applied to the user (true) or the target (false) - * @param condition {@linkcode MoveConditionFunc} optional condition to trigger the stat change - * @param firstHitOnly whether the stat change only applies on the first hit of a multi hit move - * @param moveEffectTrigger {@linkcode MoveEffectTrigger} the trigger for the effect to take place - * @param firstTargetOnly whether, if this is a multi target move, to only apply the effect after the first target is hit, rather than once for each target - * @param lastHitOnly whether the effect should only apply after the last hit of a multi hit move + * + * @param stats {@linkcode BattleStat} Array of stat(s) to change + * @param stages How many stages to change the stat(s) by, [-6, 6] + * @param selfTarget `true` if the move is self-targetting + * @param condition {@linkcode MoveConditionFunc} Optional condition to be checked in order to apply the changes + * @param showMessage `true` to display a message; default `true` + * @param firstHitOnly `true` if only the first hit of a multi hit move should cause a stat stage change; default `false` + * @param moveEffectTrigger {@linkcode MoveEffectTrigger} When the stat change should trigger; default {@linkcode MoveEffectTrigger.HIT} + * @param firstTargetOnly `true` if a move that hits multiple pokemon should only trigger the stat change if it hits at least one pokemon, rather than once per hit pokemon; default `false` + * @param lastHitOnly `true` if the effect should only apply after the last hit of a multi hit move; default `false` + * @param effectChanceOverride Will override the move's normal secondary effect chance if specified * * @extends MoveEffectAttr * @see {@linkcode apply} @@ -2767,14 +2790,14 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { export class StatStageChangeAttr extends MoveEffectAttr { public stats: BattleStat[]; public stages: integer; - private condition: MoveConditionFunc | null; + private condition?: MoveConditionFunc | null; private showMessage: boolean; - constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false, lastHitOnly: boolean = false) { - super(selfTarget, moveEffectTrigger, firstHitOnly, lastHitOnly, firstTargetOnly); + constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false, lastHitOnly: boolean = false, effectChanceOverride?: number) { + super(selfTarget, moveEffectTrigger, firstHitOnly, lastHitOnly, firstTargetOnly, effectChanceOverride); this.stats = stats; this.stages = stages; - this.condition = condition!; // TODO: is this bang correct? + this.condition = condition; this.showMessage = showMessage; } @@ -2822,26 +2845,26 @@ export class StatStageChangeAttr extends MoveEffectAttr { } let noEffect = false; switch (stat) { - case Stat.ATK: - if (this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); - } - break; - case Stat.DEF: - if (!this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); - } - break; - case Stat.SPATK: - if (this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); - } - break; - case Stat.SPDEF: - if (!this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); - } - break; + case Stat.ATK: + if (this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); + } + break; + case Stat.DEF: + if (!this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); + } + break; + case Stat.SPATK: + if (this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); + } + break; + case Stat.SPDEF: + if (!this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); + } + break; } if (noEffect) { continue; @@ -2852,6 +2875,162 @@ export class StatStageChangeAttr extends MoveEffectAttr { } } +/** + * Attribute used to determine the Biome/Terrain-based secondary effect of Secret Power + */ +export class SecretPowerAttr extends MoveEffectAttr { + constructor() { + super(false); + } + + /** + * Used to determine if the move should apply a secondary effect based on Secret Power's 30% chance + * @returns `true` if the move's secondary effect should apply + */ + override canApply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { + this.effectChanceOverride = move.chance; + const moveChance = this.getMoveChance(user, target, move, this.selfTarget); + if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { + return true; + } else { + return false; + } + } + + /** + * Used to apply the secondary effect to the target Pokemon + * @returns `true` if a secondary effect is successfully applied + */ + override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise { + if (!super.apply(user, target, move, args)) { + return false; + } + let secondaryEffect: MoveEffectAttr; + const terrain = user.scene.arena.getTerrainType(); + if (terrain !== TerrainType.NONE) { + secondaryEffect = this.determineTerrainEffect(terrain); + } else { + const biome = user.scene.arena.biomeType; + secondaryEffect = this.determineBiomeEffect(biome); + } + // effectChanceOverride used in the application of the actual secondary effect + secondaryEffect.effectChanceOverride = 100; + return secondaryEffect.apply(user, target, move, []); + } + + /** + * Determines the secondary effect based on terrain. + * Takes precedence over biome-based effects. + * ``` + * Electric Terrain | Paralysis + * Misty Terrain | SpAtk -1 + * Grassy Terrain | Sleep + * Psychic Terrain | Speed -1 + * ``` + * @param terrain - {@linkcode TerrainType} The current terrain + * @returns the chosen secondary effect {@linkcode MoveEffectAttr} + */ + private determineTerrainEffect(terrain: TerrainType): MoveEffectAttr { + let secondaryEffect: MoveEffectAttr; + switch (terrain) { + case TerrainType.ELECTRIC: + default: + secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); + break; + case TerrainType.MISTY: + secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); + break; + case TerrainType.GRASSY: + secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false); + break; + case TerrainType.PSYCHIC: + secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); + break; + } + return secondaryEffect; + } + + /** + * Determines the secondary effect based on biome + * ``` + * Town, Metropolis, Slum, Dojo, Laboratory, Power Plant + Default | Paralysis + * Plains, Grass, Tall Grass, Forest, Jungle, Meadow | Sleep + * Swamp, Mountain, Temple, Ruins | Speed -1 + * Ice Cave, Snowy Forest | Freeze + * Volcano | Burn + * Fairy Cave | SpAtk -1 + * Desert, Construction Site, Beach, Island, Badlands | Accuracy -1 + * Sea, Lake, Seabed | Atk -1 + * Cave, Wasteland, Graveyard, Abyss, Space | Flinch + * End | Def -1 + * ``` + * @param biome - The current {@linkcode Biome} the battle is set in + * @returns the chosen secondary effect {@linkcode MoveEffectAttr} + */ + private determineBiomeEffect(biome: Biome): MoveEffectAttr { + let secondaryEffect: MoveEffectAttr; + switch (biome) { + case Biome.PLAINS: + case Biome.GRASS: + case Biome.TALL_GRASS: + case Biome.FOREST: + case Biome.JUNGLE: + case Biome.MEADOW: + secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false); + break; + case Biome.SWAMP: + case Biome.MOUNTAIN: + case Biome.TEMPLE: + case Biome.RUINS: + secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); + break; + case Biome.ICE_CAVE: + case Biome.SNOWY_FOREST: + secondaryEffect = new StatusEffectAttr(StatusEffect.FREEZE, false); + break; + case Biome.VOLCANO: + secondaryEffect = new StatusEffectAttr(StatusEffect.BURN, false); + break; + case Biome.FAIRY_CAVE: + secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); + break; + case Biome.DESERT: + case Biome.CONSTRUCTION_SITE: + case Biome.BEACH: + case Biome.ISLAND: + case Biome.BADLANDS: + secondaryEffect = new StatStageChangeAttr([ Stat.ACC ], -1, false); + break; + case Biome.SEA: + case Biome.LAKE: + case Biome.SEABED: + secondaryEffect = new StatStageChangeAttr([ Stat.ATK ], -1, false); + break; + case Biome.CAVE: + case Biome.WASTELAND: + case Biome.GRAVEYARD: + case Biome.ABYSS: + case Biome.SPACE: + secondaryEffect = new AddBattlerTagAttr(BattlerTagType.FLINCHED, false, true); + break; + case Biome.END: + secondaryEffect = new StatStageChangeAttr([ Stat.DEF ], -1, false); + break; + case Biome.TOWN: + case Biome.METROPOLIS: + case Biome.SLUM: + case Biome.DOJO: + case Biome.FACTORY: + case Biome.LABORATORY: + case Biome.POWER_PLANT: + default: + secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); + break; + } + return secondaryEffect; + } +} + export class PostVictoryStatStageChangeAttr extends MoveAttr { private stats: BattleStat[]; private stages: number; @@ -3130,21 +3309,21 @@ export class LessPPMorePowerAttr extends VariablePowerAttr { const power = args[0] as Utils.NumberHolder; switch (ppRemains) { - case 0: - power.value = 200; - break; - case 1: - power.value = 80; - break; - case 2: - power.value = 60; - break; - case 3: - power.value = 50; - break; - default: - power.value = 40; - break; + case 0: + power.value = 200; + break; + case 1: + power.value = 80; + break; + case 2: + power.value = 60; + break; + case 3: + power.value = 50; + break; + default: + power.value = 40; + break; } return true; } @@ -3364,24 +3543,24 @@ export class LowHpPowerAttr extends VariablePowerAttr { const hpRatio = user.getHpRatio(); switch (true) { - case (hpRatio < 0.0417): - power.value = 200; - break; - case (hpRatio < 0.1042): - power.value = 150; - break; - case (hpRatio < 0.2083): - power.value = 100; - break; - case (hpRatio < 0.3542): - power.value = 80; - break; - case (hpRatio < 0.6875): - power.value = 40; - break; - default: - power.value = 20; - break; + case (hpRatio < 0.0417): + power.value = 200; + break; + case (hpRatio < 0.1042): + power.value = 150; + break; + case (hpRatio < 0.2083): + power.value = 100; + break; + case (hpRatio < 0.3542): + power.value = 80; + break; + case (hpRatio < 0.6875): + power.value = 40; + break; + default: + power.value = 20; + break; } return true; @@ -3401,21 +3580,21 @@ export class CompareWeightPowerAttr extends VariablePowerAttr { const relativeWeight = (targetWeight / userWeight) * 100; switch (true) { - case (relativeWeight < 20.01): - power.value = 120; - break; - case (relativeWeight < 25.01): - power.value = 100; - break; - case (relativeWeight < 33.35): - power.value = 80; - break; - case (relativeWeight < 50.01): - power.value = 60; - break; - default: - power.value = 40; - break; + case (relativeWeight < 20.01): + power.value = 120; + break; + case (relativeWeight < 25.01): + power.value = 100; + break; + case (relativeWeight < 33.35): + power.value = 80; + break; + case (relativeWeight < 50.01): + power.value = 60; + break; + default: + power.value = 40; + break; } return true; @@ -3531,13 +3710,13 @@ export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr { const power = args[0] as Utils.NumberHolder; const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { - case WeatherType.RAIN: - case WeatherType.SANDSTORM: - case WeatherType.HAIL: - case WeatherType.SNOW: - case WeatherType.HEAVY_RAIN: - power.value *= 0.5; - return true; + case WeatherType.RAIN: + case WeatherType.SANDSTORM: + case WeatherType.HAIL: + case WeatherType.SNOW: + case WeatherType.HEAVY_RAIN: + power.value *= 0.5; + return true; } } @@ -3811,8 +3990,8 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr { for (const p of pokemonActed) { const [ lastMove ] = p.getLastXMoves(1); - if (lastMove.result !== MoveResult.FAIL) { - if ((lastMove.result === MoveResult.SUCCESS) && (lastMove.move === this.move)) { + if (lastMove?.result !== MoveResult.FAIL) { + if ((lastMove?.result === MoveResult.SUCCESS) && (lastMove?.move === this.move)) { power.value *= 2; return true; } else { @@ -3934,14 +4113,14 @@ export class ThunderAccuracyAttr extends VariableAccuracyAttr { const accuracy = args[0] as Utils.NumberHolder; const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - accuracy.value = 50; - return true; - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - accuracy.value = -1; - return true; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + accuracy.value = 50; + return true; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + accuracy.value = -1; + return true; } } @@ -3960,10 +4139,10 @@ export class StormAccuracyAttr extends VariableAccuracyAttr { const accuracy = args[0] as Utils.NumberHolder; const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - accuracy.value = -1; - return true; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + accuracy.value = -1; + return true; } } @@ -4101,11 +4280,11 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr { * @param user {@linkcode Pokemon} using the move * @param target {@linkcode Pokemon} target of the move * @param move {@linkcode Move} with this attribute - * @param args [0] {@linkcode Utils.IntegerHolder} The category of the move + * @param args [0] {@linkcode Utils.NumberHolder} The category of the move * @returns true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const category = (args[0] as Utils.IntegerHolder); + const category = (args[0] as Utils.NumberHolder); if (user.getAlly() === target) { category.value = MoveCategory.STATUS; @@ -4193,21 +4372,21 @@ export class TechnoBlastTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Shock Drive - moveType.value = Type.ELECTRIC; - break; - case 2: // Burn Drive - moveType.value = Type.FIRE; - break; - case 3: // Chill Drive - moveType.value = Type.ICE; - break; - case 4: // Douse Drive - moveType.value = Type.WATER; - break; - default: - moveType.value = Type.NORMAL; - break; + case 1: // Shock Drive + moveType.value = Type.ELECTRIC; + break; + case 2: // Burn Drive + moveType.value = Type.FIRE; + break; + case 3: // Chill Drive + moveType.value = Type.ICE; + break; + case 4: // Douse Drive + moveType.value = Type.WATER; + break; + default: + moveType.value = Type.NORMAL; + break; } return true; } @@ -4227,12 +4406,12 @@ export class AuraWheelTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Hangry Mode - moveType.value = Type.DARK; - break; - default: // Full Belly Mode - moveType.value = Type.ELECTRIC; - break; + case 1: // Hangry Mode + moveType.value = Type.DARK; + break; + default: // Full Belly Mode + moveType.value = Type.ELECTRIC; + break; } return true; } @@ -4252,15 +4431,15 @@ export class RagingBullTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Blaze breed - moveType.value = Type.FIRE; - break; - case 2: // Aqua breed - moveType.value = Type.WATER; - break; - default: - moveType.value = Type.FIGHTING; - break; + case 1: // Blaze breed + moveType.value = Type.FIRE; + break; + case 2: // Aqua breed + moveType.value = Type.WATER; + break; + default: + moveType.value = Type.FIGHTING; + break; } return true; } @@ -4280,22 +4459,22 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Wellspring Mask - case 5: // Wellspring Mask Tera - moveType.value = Type.WATER; - break; - case 2: // Hearthflame Mask - case 6: // Hearthflame Mask Tera - moveType.value = Type.FIRE; - break; - case 3: // Cornerstone Mask - case 7: // Cornerstone Mask Tera - moveType.value = Type.ROCK; - break; - case 4: // Teal Mask Tera - default: - moveType.value = Type.GRASS; - break; + case 1: // Wellspring Mask + case 5: // Wellspring Mask Tera + moveType.value = Type.WATER; + break; + case 2: // Hearthflame Mask + case 6: // Hearthflame Mask Tera + moveType.value = Type.FIRE; + break; + case 3: // Cornerstone Mask + case 7: // Cornerstone Mask Tera + moveType.value = Type.ROCK; + break; + case 4: // Teal Mask Tera + default: + moveType.value = Type.GRASS; + break; } return true; } @@ -4313,23 +4492,23 @@ export class WeatherBallTypeAttr extends VariableMoveTypeAttr { if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { switch (user.scene.arena.weather?.weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - moveType.value = Type.FIRE; - break; - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - moveType.value = Type.WATER; - break; - case WeatherType.SANDSTORM: - moveType.value = Type.ROCK; - break; - case WeatherType.HAIL: - case WeatherType.SNOW: - moveType.value = Type.ICE; - break; - default: - return false; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + moveType.value = Type.FIRE; + break; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + moveType.value = Type.WATER; + break; + case WeatherType.SANDSTORM: + moveType.value = Type.ROCK; + break; + case WeatherType.HAIL: + case WeatherType.SNOW: + moveType.value = Type.ICE; + break; + default: + return false; } return true; } @@ -4364,20 +4543,20 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr { const currentTerrain = user.scene.arena.getTerrainType(); switch (currentTerrain) { - case TerrainType.MISTY: - moveType.value = Type.FAIRY; - break; - case TerrainType.ELECTRIC: - moveType.value = Type.ELECTRIC; - break; - case TerrainType.GRASSY: - moveType.value = Type.GRASS; - break; - case TerrainType.PSYCHIC: - moveType.value = Type.PSYCHIC; - break; - default: - return false; + case TerrainType.MISTY: + moveType.value = Type.FAIRY; + break; + case TerrainType.ELECTRIC: + moveType.value = Type.ELECTRIC; + break; + case TerrainType.GRASSY: + moveType.value = Type.GRASS; + break; + case TerrainType.PSYCHIC: + moveType.value = Type.PSYCHIC; + break; + default: + return false; } return true; } @@ -4477,26 +4656,26 @@ export class CombinedPledgeTypeAttr extends VariableMoveTypeAttr { } switch (move.id) { - case Moves.FIRE_PLEDGE: - if (combinedPledgeMove === Moves.WATER_PLEDGE) { - moveType.value = Type.WATER; - return true; - } - return false; - case Moves.WATER_PLEDGE: - if (combinedPledgeMove === Moves.GRASS_PLEDGE) { - moveType.value = Type.GRASS; - return true; - } - return false; - case Moves.GRASS_PLEDGE: - if (combinedPledgeMove === Moves.FIRE_PLEDGE) { - moveType.value = Type.FIRE; - return true; - } - return false; - default: - return false; + case Moves.FIRE_PLEDGE: + if (combinedPledgeMove === Moves.WATER_PLEDGE) { + moveType.value = Type.WATER; + return true; + } + return false; + case Moves.WATER_PLEDGE: + if (combinedPledgeMove === Moves.GRASS_PLEDGE) { + moveType.value = Type.GRASS; + return true; + } + return false; + case Moves.GRASS_PLEDGE: + if (combinedPledgeMove === Moves.FIRE_PLEDGE) { + moveType.value = Type.FIRE; + return true; + } + return false; + default: + return false; } } } @@ -4541,18 +4720,19 @@ export class WaterSuperEffectTypeMultiplierAttr extends VariableMoveTypeMultipli export class IceNoEffectTypeAttr extends VariableMoveTypeMultiplierAttr { /** * Checks to see if the Target is Ice-Type or not. If so, the move will have no effect. - * @param {Pokemon} user N/A - * @param {Pokemon} target Pokemon that is being checked whether Ice-Type or not. - * @param {Move} move N/A - * @param {any[]} args Sets to false if the target is Ice-Type, so it should do no damage/no effect. - * @returns {boolean} Returns true if move is successful, false if Ice-Type. + * @param user n/a + * @param target The {@linkcode Pokemon} targeted by the move + * @param move n/a + * @param args `[0]` a {@linkcode Utils.NumberHolder | NumberHolder} containing a type effectiveness multiplier + * @returns `true` if this Ice-type immunity applies; `false` otherwise */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + const multiplier = args[0] as Utils.NumberHolder; if (target.isOfType(Type.ICE)) { - (args[0] as Utils.BooleanHolder).value = false; - return false; + multiplier.value = 0; + return true; } - return true; + return false; } } @@ -4712,7 +4892,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr { } canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0].result === MoveResult.FAIL)) { + if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0]?.result === MoveResult.FAIL)) { return false; } else { return true; @@ -4740,48 +4920,48 @@ export class AddBattlerTagAttr extends MoveEffectAttr { getTagTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer | void { switch (this.tagType) { - case BattlerTagType.RECHARGING: - case BattlerTagType.PERISH_SONG: - return -16; - case BattlerTagType.FLINCHED: - case BattlerTagType.CONFUSED: - case BattlerTagType.INFATUATED: - case BattlerTagType.NIGHTMARE: - case BattlerTagType.DROWSY: - case BattlerTagType.DISABLED: - case BattlerTagType.HEAL_BLOCK: - case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: - return -5; - case BattlerTagType.SEEDED: - case BattlerTagType.SALT_CURED: - case BattlerTagType.CURSED: - case BattlerTagType.FRENZY: - case BattlerTagType.TRAPPED: - case BattlerTagType.BIND: - case BattlerTagType.WRAP: - case BattlerTagType.FIRE_SPIN: - case BattlerTagType.WHIRLPOOL: - case BattlerTagType.CLAMP: - case BattlerTagType.SAND_TOMB: - case BattlerTagType.MAGMA_STORM: - case BattlerTagType.SNAP_TRAP: - case BattlerTagType.THUNDER_CAGE: - case BattlerTagType.INFESTATION: - return -3; - case BattlerTagType.ENCORE: - return -2; - case BattlerTagType.MINIMIZED: - case BattlerTagType.ALWAYS_GET_HIT: - return 0; - case BattlerTagType.INGRAIN: - case BattlerTagType.IGNORE_ACCURACY: - case BattlerTagType.AQUA_RING: - return 3; - case BattlerTagType.PROTECTED: - case BattlerTagType.FLYING: - case BattlerTagType.CRIT_BOOST: - case BattlerTagType.ALWAYS_CRIT: - return 5; + case BattlerTagType.RECHARGING: + case BattlerTagType.PERISH_SONG: + return -16; + case BattlerTagType.FLINCHED: + case BattlerTagType.CONFUSED: + case BattlerTagType.INFATUATED: + case BattlerTagType.NIGHTMARE: + case BattlerTagType.DROWSY: + case BattlerTagType.DISABLED: + case BattlerTagType.HEAL_BLOCK: + case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: + return -5; + case BattlerTagType.SEEDED: + case BattlerTagType.SALT_CURED: + case BattlerTagType.CURSED: + case BattlerTagType.FRENZY: + case BattlerTagType.TRAPPED: + case BattlerTagType.BIND: + case BattlerTagType.WRAP: + case BattlerTagType.FIRE_SPIN: + case BattlerTagType.WHIRLPOOL: + case BattlerTagType.CLAMP: + case BattlerTagType.SAND_TOMB: + case BattlerTagType.MAGMA_STORM: + case BattlerTagType.SNAP_TRAP: + case BattlerTagType.THUNDER_CAGE: + case BattlerTagType.INFESTATION: + return -3; + case BattlerTagType.ENCORE: + return -2; + case BattlerTagType.MINIMIZED: + case BattlerTagType.ALWAYS_GET_HIT: + return 0; + case BattlerTagType.INGRAIN: + case BattlerTagType.IGNORE_ACCURACY: + case BattlerTagType.AQUA_RING: + return 3; + case BattlerTagType.PROTECTED: + case BattlerTagType.FLYING: + case BattlerTagType.CRIT_BOOST: + case BattlerTagType.ALWAYS_CRIT: + return 5; } } @@ -4829,14 +5009,14 @@ export class GulpMissileTagAttr extends MoveEffectAttr { /** * Adds BattlerTagType from GulpMissileTag based on the Pokemon's HP ratio. - * @param {Pokemon} user The Pokemon using the move. - * @param {Pokemon} target The Pokemon being targeted by the move. - * @param {Move} move The move being used. - * @param {any[]} args Additional arguments, if any. + * @param user The Pokemon using the move. + * @param _target N/A + * @param move The move being used. + * @param _args N/A * @returns Whether the BattlerTag is applied. */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise { - if (!super.apply(user, target, move, args)) { + apply(user: Pokemon, _target: Pokemon, move: Move, _args: any[]): boolean { + if (!super.apply(user, _target, move, _args)) { return false; } @@ -5150,7 +5330,7 @@ export class AddArenaTagAttr extends MoveEffectAttr { return false; } - if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0].result === MoveResult.SUCCESS) { + if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); return true; } @@ -5225,7 +5405,7 @@ export class AddArenaTrapTagHitAttr extends AddArenaTagAttr { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag; - if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) && user.getLastXMoves(1)[0].result === MoveResult.SUCCESS) { + if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { user.scene.arena.addTag(this.tagType, 0, move.id, user.id, side); if (!tag) { return true; @@ -5362,7 +5542,7 @@ export class AddPledgeEffectAttr extends AddArenaTagAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { // TODO: add support for `HIT` effect triggering in AddArenaTagAttr to remove the need for this check - if (user.getLastXMoves(1)[0].result !== MoveResult.SUCCESS) { + if (user.getLastXMoves(1)[0]?.result !== MoveResult.SUCCESS) { return false; } @@ -5460,37 +5640,38 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { */ const switchOutTarget = this.selfSwitch ? user : target; if (switchOutTarget instanceof PlayerPokemon) { + // Switch out logic for the player's Pokemon if (switchOutTarget.scene.getParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { return false; } - switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); if (switchOutTarget.hp > 0) { + switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); user.scene.prependToPhase(new SwitchPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase); return true; } return false; } else if (user.scene.currentBattle.battleType !== BattleType.WILD) { + // Switch out logic for trainer battles if (switchOutTarget.scene.getEnemyParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { return false; } - // Switch out logic for trainer battles - switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); if (switchOutTarget.hp > 0) { // for opponent switching out + switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); user.scene.prependToPhase(new SwitchSummonPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(), (user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), false, false), MoveEndPhase); } } else { + // Switch out logic for everything else (eg: WILD battles) if (user.scene.currentBattle.waveIndex % 10 === 0) { return false; } - // Switch out logic for everything else (eg: WILD battles) - switchOutTarget.leaveField(false); - if (switchOutTarget.hp) { + if (switchOutTarget.hp > 0) { + switchOutTarget.leaveField(false); user.scene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); // in double battles redirect potential moves off fled pokemon @@ -5753,19 +5934,19 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { } let selectTargets: BattlerIndex[]; switch (true) { - case (moveTargets.multiple || moveTargets.targets.length === 1): { - selectTargets = moveTargets.targets; - break; - } - case (moveTargets.targets.indexOf(target.getBattlerIndex()) > -1): { - selectTargets = [ target.getBattlerIndex() ]; - break; - } - default: { - moveTargets.targets.splice(moveTargets.targets.indexOf(user.getAlly().getBattlerIndex())); - selectTargets = [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; - break; - } + case (moveTargets.multiple || moveTargets.targets.length === 1): { + selectTargets = moveTargets.targets; + break; + } + case (moveTargets.targets.indexOf(target.getBattlerIndex()) > -1): { + selectTargets = [ target.getBattlerIndex() ]; + break; + } + default: { + moveTargets.targets.splice(moveTargets.targets.indexOf(user.getAlly().getBattlerIndex())); + selectTargets = [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; + break; + } } const targets = selectTargets; user.getMoveQueue().push({ move: move?.moveId!, targets: targets, ignorePP: true }); // TODO: is this bang correct? @@ -5809,131 +5990,131 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { let moveId; switch (user.scene.arena.getTerrainType()) { // this allows terrains to 'override' the biome move - case TerrainType.NONE: - switch (user.scene.arena.biomeType) { - case Biome.TOWN: - moveId = Moves.ROUND; + case TerrainType.NONE: + switch (user.scene.arena.biomeType) { + case Biome.TOWN: + moveId = Moves.ROUND; + break; + case Biome.METROPOLIS: + moveId = Moves.TRI_ATTACK; + break; + case Biome.SLUM: + moveId = Moves.SLUDGE_BOMB; + break; + case Biome.PLAINS: + moveId = Moves.SILVER_WIND; + break; + case Biome.GRASS: + moveId = Moves.GRASS_KNOT; + break; + case Biome.TALL_GRASS: + moveId = Moves.POLLEN_PUFF; + break; + case Biome.MEADOW: + moveId = Moves.GIGA_DRAIN; + break; + case Biome.FOREST: + moveId = Moves.BUG_BUZZ; + break; + case Biome.JUNGLE: + moveId = Moves.LEAF_STORM; + break; + case Biome.SEA: + moveId = Moves.HYDRO_PUMP; + break; + case Biome.SWAMP: + moveId = Moves.MUD_BOMB; + break; + case Biome.BEACH: + moveId = Moves.SCALD; + break; + case Biome.LAKE: + moveId = Moves.BUBBLE_BEAM; + break; + case Biome.SEABED: + moveId = Moves.BRINE; + break; + case Biome.ISLAND: + moveId = Moves.LEAF_TORNADO; + break; + case Biome.MOUNTAIN: + moveId = Moves.AIR_SLASH; + break; + case Biome.BADLANDS: + moveId = Moves.EARTH_POWER; + break; + case Biome.DESERT: + moveId = Moves.SCORCHING_SANDS; + break; + case Biome.WASTELAND: + moveId = Moves.DRAGON_PULSE; + break; + case Biome.CONSTRUCTION_SITE: + moveId = Moves.STEEL_BEAM; + break; + case Biome.CAVE: + moveId = Moves.POWER_GEM; + break; + case Biome.ICE_CAVE: + moveId = Moves.ICE_BEAM; + break; + case Biome.SNOWY_FOREST: + moveId = Moves.FROST_BREATH; + break; + case Biome.VOLCANO: + moveId = Moves.LAVA_PLUME; + break; + case Biome.GRAVEYARD: + moveId = Moves.SHADOW_BALL; + break; + case Biome.RUINS: + moveId = Moves.ANCIENT_POWER; + break; + case Biome.TEMPLE: + moveId = Moves.EXTRASENSORY; + break; + case Biome.DOJO: + moveId = Moves.FOCUS_BLAST; + break; + case Biome.FAIRY_CAVE: + moveId = Moves.ALLURING_VOICE; + break; + case Biome.ABYSS: + moveId = Moves.OMINOUS_WIND; + break; + case Biome.SPACE: + moveId = Moves.DRACO_METEOR; + break; + case Biome.FACTORY: + moveId = Moves.FLASH_CANNON; + break; + case Biome.LABORATORY: + moveId = Moves.ZAP_CANNON; + break; + case Biome.POWER_PLANT: + moveId = Moves.CHARGE_BEAM; + break; + case Biome.END: + moveId = Moves.ETERNABEAM; + break; + } break; - case Biome.METROPOLIS: + case TerrainType.MISTY: + moveId = Moves.MOONBLAST; + break; + case TerrainType.ELECTRIC: + moveId = Moves.THUNDERBOLT; + break; + case TerrainType.GRASSY: + moveId = Moves.ENERGY_BALL; + break; + case TerrainType.PSYCHIC: + moveId = Moves.PSYCHIC; + break; + default: + // Just in case there's no match moveId = Moves.TRI_ATTACK; break; - case Biome.SLUM: - moveId = Moves.SLUDGE_BOMB; - break; - case Biome.PLAINS: - moveId = Moves.SILVER_WIND; - break; - case Biome.GRASS: - moveId = Moves.GRASS_KNOT; - break; - case Biome.TALL_GRASS: - moveId = Moves.POLLEN_PUFF; - break; - case Biome.MEADOW: - moveId = Moves.GIGA_DRAIN; - break; - case Biome.FOREST: - moveId = Moves.BUG_BUZZ; - break; - case Biome.JUNGLE: - moveId = Moves.LEAF_STORM; - break; - case Biome.SEA: - moveId = Moves.HYDRO_PUMP; - break; - case Biome.SWAMP: - moveId = Moves.MUD_BOMB; - break; - case Biome.BEACH: - moveId = Moves.SCALD; - break; - case Biome.LAKE: - moveId = Moves.BUBBLE_BEAM; - break; - case Biome.SEABED: - moveId = Moves.BRINE; - break; - case Biome.ISLAND: - moveId = Moves.LEAF_TORNADO; - break; - case Biome.MOUNTAIN: - moveId = Moves.AIR_SLASH; - break; - case Biome.BADLANDS: - moveId = Moves.EARTH_POWER; - break; - case Biome.DESERT: - moveId = Moves.SCORCHING_SANDS; - break; - case Biome.WASTELAND: - moveId = Moves.DRAGON_PULSE; - break; - case Biome.CONSTRUCTION_SITE: - moveId = Moves.STEEL_BEAM; - break; - case Biome.CAVE: - moveId = Moves.POWER_GEM; - break; - case Biome.ICE_CAVE: - moveId = Moves.ICE_BEAM; - break; - case Biome.SNOWY_FOREST: - moveId = Moves.FROST_BREATH; - break; - case Biome.VOLCANO: - moveId = Moves.LAVA_PLUME; - break; - case Biome.GRAVEYARD: - moveId = Moves.SHADOW_BALL; - break; - case Biome.RUINS: - moveId = Moves.ANCIENT_POWER; - break; - case Biome.TEMPLE: - moveId = Moves.EXTRASENSORY; - break; - case Biome.DOJO: - moveId = Moves.FOCUS_BLAST; - break; - case Biome.FAIRY_CAVE: - moveId = Moves.ALLURING_VOICE; - break; - case Biome.ABYSS: - moveId = Moves.OMINOUS_WIND; - break; - case Biome.SPACE: - moveId = Moves.DRACO_METEOR; - break; - case Biome.FACTORY: - moveId = Moves.FLASH_CANNON; - break; - case Biome.LABORATORY: - moveId = Moves.ZAP_CANNON; - break; - case Biome.POWER_PLANT: - moveId = Moves.CHARGE_BEAM; - break; - case Biome.END: - moveId = Moves.ETERNABEAM; - break; - } - break; - case TerrainType.MISTY: - moveId = Moves.MOONBLAST; - break; - case TerrainType.ELECTRIC: - moveId = Moves.THUNDERBOLT; - break; - case TerrainType.GRASSY: - moveId = Moves.ENERGY_BALL; - break; - case TerrainType.PSYCHIC: - moveId = Moves.PSYCHIC; - break; - default: - // Just in case there's no match - moveId = Moves.TRI_ATTACK; - break; } user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true }); @@ -6405,6 +6586,9 @@ export class TransformAttr extends MoveEffectAttr { user.summonData.gender = target.getGender(); user.summonData.fusionGender = target.getFusionGender(); + // Power Trick's effect will not preserved after using Transform + user.removeTag(BattlerTagType.POWER_TRICK); + // Copy all stats (except HP) for (const s of EFFECTIVE_STATS) { user.setStat(s, target.getStat(s, false), false); @@ -6968,47 +7152,47 @@ export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet { let multiple = false; switch (moveTarget) { - case MoveTarget.USER: - case MoveTarget.PARTY: - set = [ user ]; - break; - case MoveTarget.NEAR_OTHER: - case MoveTarget.OTHER: - case MoveTarget.ALL_NEAR_OTHERS: - case MoveTarget.ALL_OTHERS: - set = (opponents.concat([ user.getAlly() ])); - multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS; - break; - case MoveTarget.NEAR_ENEMY: - case MoveTarget.ALL_NEAR_ENEMIES: - case MoveTarget.ALL_ENEMIES: - case MoveTarget.ENEMY_SIDE: - set = opponents; - multiple = moveTarget !== MoveTarget.NEAR_ENEMY; - break; - case MoveTarget.RANDOM_NEAR_ENEMY: - set = [ opponents[user.randSeedInt(opponents.length)] ]; - break; - case MoveTarget.ATTACKER: - return { targets: [ -1 as BattlerIndex ], multiple: false }; - case MoveTarget.NEAR_ALLY: - case MoveTarget.ALLY: - set = [ user.getAlly() ]; - break; - case MoveTarget.USER_OR_NEAR_ALLY: - case MoveTarget.USER_AND_ALLIES: - case MoveTarget.USER_SIDE: - set = [ user, user.getAlly() ]; - multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; - break; - case MoveTarget.ALL: - case MoveTarget.BOTH_SIDES: - set = [ user, user.getAlly() ].concat(opponents); - multiple = true; - break; - case MoveTarget.CURSE: - set = user.getTypes(true).includes(Type.GHOST) ? (opponents.concat([ user.getAlly() ])) : [ user ]; - break; + case MoveTarget.USER: + case MoveTarget.PARTY: + set = [ user ]; + break; + case MoveTarget.NEAR_OTHER: + case MoveTarget.OTHER: + case MoveTarget.ALL_NEAR_OTHERS: + case MoveTarget.ALL_OTHERS: + set = (opponents.concat([ user.getAlly() ])); + multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS; + break; + case MoveTarget.NEAR_ENEMY: + case MoveTarget.ALL_NEAR_ENEMIES: + case MoveTarget.ALL_ENEMIES: + case MoveTarget.ENEMY_SIDE: + set = opponents; + multiple = moveTarget !== MoveTarget.NEAR_ENEMY; + break; + case MoveTarget.RANDOM_NEAR_ENEMY: + set = [ opponents[user.randSeedInt(opponents.length)] ]; + break; + case MoveTarget.ATTACKER: + return { targets: [ -1 as BattlerIndex ], multiple: false }; + case MoveTarget.NEAR_ALLY: + case MoveTarget.ALLY: + set = [ user.getAlly() ]; + break; + case MoveTarget.USER_OR_NEAR_ALLY: + case MoveTarget.USER_AND_ALLIES: + case MoveTarget.USER_SIDE: + set = [ user, user.getAlly() ]; + multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; + break; + case MoveTarget.ALL: + case MoveTarget.BOTH_SIDES: + set = [ user, user.getAlly() ].concat(opponents); + multiple = true; + break; + case MoveTarget.CURSE: + set = user.getTypes(true).includes(Type.GHOST) ? (opponents.concat([ user.getAlly() ])) : [ user ]; + break; } return { targets: set.filter(p => p?.isActive(true)).map(p => p.getBattlerIndex()).filter(t => t !== undefined), multiple }; @@ -7068,7 +7252,8 @@ export function initMoves() { .attr(ForceSwitchOutAttr) .ignoresSubstitute() .hidesTarget() - .windMove(), + .windMove() + .partial(), // Should force random switches new AttackMove(Moves.FLY, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, -1, 0, 1) .attr(ChargeAttr, ChargeAnim.FLY_CHARGING, i18next.t("moveTriggers:flewUpHigh", { pokemonName: "{USER}" }), BattlerTagType.FLYING) .condition(failOnGravityCondition) @@ -7145,7 +7330,8 @@ export function initMoves() { new StatusMove(Moves.ROAR, Type.NORMAL, -1, 20, -1, -6, 1) .attr(ForceSwitchOutAttr) .soundBased() - .hidesTarget(), + .hidesTarget() + .partial(), // Should force random switching new StatusMove(Moves.SING, Type.NORMAL, 55, 15, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.SLEEP) .soundBased(), @@ -7286,7 +7472,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true), new AttackMove(Moves.QUICK_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 1), new AttackMove(Moves.RAGE, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 20, -1, 0, 1) - .partial(), + .partial(), // No effect implemented new SelfStatusMove(Moves.TELEPORT, Type.PSYCHIC, -1, 20, -1, -6, 1) .attr(ForceSwitchOutAttr, true) .hidesUser(), @@ -7601,7 +7787,7 @@ export function initMoves() { new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2) .attr(StatStageChangeAttr, [ Stat.ATK ], -2), new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2) - .partial() + .partial() // Does not lock the user, also does not increase damage properly .attr(ConsecutiveUseDoublePowerAttr, 5, true, true, Moves.DEFENSE_CURL), new AttackMove(Moves.FALSE_SWIPE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 2) .attr(SurviveDamageAttr), @@ -7672,7 +7858,7 @@ export function initMoves() { .ignoresSubstitute() .condition((user, target, move) => new EncoreTag(user.id).canAdd(target)), new AttackMove(Moves.PURSUIT, Type.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 2) - .partial(), + .partial(), // No effect implemented new AttackMove(Moves.RAPID_SPIN, Type.NORMAL, MoveCategory.PHYSICAL, 50, 100, 40, 100, 0, 2) .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true) .attr(RemoveBattlerTagAttr, [ @@ -7737,7 +7923,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1) .ballBombMove(), new AttackMove(Moves.FUTURE_SIGHT, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 2) - .partial() + .partial() // Complete buggy mess .attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, i18next.t("moveTriggers:foresawAnAttack", { pokemonName: "{USER}" })), new AttackMove(Moves.ROCK_SMASH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2) .attr(StatStageChangeAttr, [ Stat.DEF ], -1), @@ -7755,7 +7941,7 @@ export function initMoves() { .ignoresVirtual() .soundBased() .target(MoveTarget.RANDOM_NEAR_ENEMY) - .partial(), + .partial(), // Does not lock the user, does not stop Pokemon from sleeping new SelfStatusMove(Moves.STOCKPILE, Type.NORMAL, -1, 20, -1, 0, 3) .condition(user => (user.getTag(StockpilingTag)?.stockpiledCount ?? 0) < 3) .attr(AddBattlerTagAttr, BattlerTagType.STOCKPILING, true), @@ -7778,7 +7964,7 @@ export function initMoves() { .target(MoveTarget.BOTH_SIDES), new StatusMove(Moves.TORMENT, Type.DARK, 100, 15, -1, 0, 3) .ignoresSubstitute() - .partial() // Incomplete implementation because of Uproar's partial implementation + .edgeCase() // Incomplete implementation because of Uproar's partial implementation .attr(AddBattlerTagAttr, BattlerTagType.TORMENT, false, true, 1), new StatusMove(Moves.FLATTER, Type.DARK, 100, 15, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.SPATK ], 1) @@ -7827,7 +8013,9 @@ export function initMoves() { .attr(RandomMovesetMoveAttr, true) .ignoresVirtual(), new SelfStatusMove(Moves.INGRAIN, Type.GRASS, -1, 20, -1, 0, 3) - .attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true), + .attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true) + .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, true, true) + .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLOATING ], true), new AttackMove(Moves.SUPERPOWER, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], -1, true), new SelfStatusMove(Moves.MAGIC_COAT, Type.PSYCHIC, -1, 15, -1, 4, 3) @@ -7855,7 +8043,8 @@ export function initMoves() { .attr(SwitchAbilitiesAttr), new StatusMove(Moves.IMPRISON, Type.PSYCHIC, 100, 10, -1, 0, 3) .ignoresSubstitute() - .attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false), + .attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false) + .target(MoveTarget.ENEMY_SIDE), new SelfStatusMove(Moves.REFRESH, Type.NORMAL, -1, 20, -1, 0, 3) .attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN) .condition((user, target, move) => !!user.status && (user.status.effect === StatusEffect.PARALYSIS || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.BURN)), @@ -7865,7 +8054,7 @@ export function initMoves() { .unimplemented(), new AttackMove(Moves.SECRET_POWER, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, 30, 0, 3) .makesContact(false) - .partial(), + .attr(SecretPowerAttr), new AttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3) .attr(ChargeAttr, ChargeAnim.DIVE_CHARGING, i18next.t("moveTriggers:hidUnderwater", { pokemonName: "{USER}" }), BattlerTagType.UNDERWATER, true) .attr(GulpMissileTagAttr) @@ -7896,7 +8085,7 @@ export function initMoves() { .attr(AddArenaTagAttr, ArenaTagType.MUD_SPORT, 5) .target(MoveTarget.BOTH_SIDES), new AttackMove(Moves.ICE_BALL, Type.ICE, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 3) - .partial() + .partial() // Does not lock the user properly, does not increase damage correctly .attr(ConsecutiveUseDoublePowerAttr, 5, true, true, Moves.DEFENSE_CURL) .ballBombMove(), new AttackMove(Moves.NEEDLE_ARM, Type.GRASS, MoveCategory.PHYSICAL, 60, 100, 15, 30, 0, 3) @@ -8039,7 +8228,7 @@ export function initMoves() { .attr(ConfuseAttr) .pulseMove(), new AttackMove(Moves.DOOM_DESIRE, Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3) - .partial() + .partial() // Complete buggy mess .attr(DelayedAttackAttr, ArenaTagType.DOOM_DESIRE, ChargeAnim.DOOM_DESIRE_CHARGING, i18next.t("moveTriggers:choseDoomDesireAsDestiny", { pokemonName: "{USER}" })), new AttackMove(Moves.PSYCHO_BOOST, Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true), @@ -8123,7 +8312,7 @@ export function initMoves() { .attr(OpponentHighHpPowerAttr, 120) .makesContact(), new SelfStatusMove(Moves.POWER_TRICK, Type.PSYCHIC, -1, 10, -1, 0, 4) - .unimplemented(), + .attr(AddBattlerTagAttr, BattlerTagType.POWER_TRICK, true), new StatusMove(Moves.GASTRO_ACID, Type.POISON, 100, 10, -1, 0, 4) .attr(SuppressAbilitiesAttr), new StatusMove(Moves.LUCKY_CHANT, Type.NORMAL, -1, 30, -1, 0, 4) @@ -8161,8 +8350,8 @@ export function initMoves() { new SelfStatusMove(Moves.AQUA_RING, Type.WATER, -1, 20, -1, 0, 4) .attr(AddBattlerTagAttr, BattlerTagType.AQUA_RING, true, true), new SelfStatusMove(Moves.MAGNET_RISE, Type.ELECTRIC, -1, 10, -1, 0, 4) - .attr(AddBattlerTagAttr, BattlerTagType.MAGNET_RISEN, true, true) - .condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && [ BattlerTagType.MAGNET_RISEN, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN ].every((tag) => !user.getTag(tag))), + .attr(AddBattlerTagAttr, BattlerTagType.FLOATING, true, true) + .condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && [ BattlerTagType.FLOATING, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN ].every((tag) => !user.getTag(tag))), new AttackMove(Moves.FLARE_BLITZ, Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 4) .attr(RecoilAttr, false, 0.33) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) @@ -8387,7 +8576,11 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true), new StatusMove(Moves.TELEKINESIS, Type.PSYCHIC, -1, 15, -1, 0, 5) .condition(failOnGravityCondition) - .unimplemented(), + .condition((_user, target, _move) => ![ Species.DIGLETT, Species.DUGTRIO, Species.ALOLA_DIGLETT, Species.ALOLA_DUGTRIO, Species.SANDYGAST, Species.PALOSSAND, Species.WIGLETT, Species.WUGTRIO ].includes(target.species.speciesId)) + .condition((_user, target, _move) => !(target.species.speciesId === Species.GENGAR && target.getFormKey() === "mega")) + .condition((_user, target, _move) => Utils.isNullOrUndefined(target.getTag(BattlerTagType.INGRAIN)) && Utils.isNullOrUndefined(target.getTag(BattlerTagType.IGNORE_FLYING))) + .attr(AddBattlerTagAttr, BattlerTagType.TELEKINESIS, false, true, 3) + .attr(AddBattlerTagAttr, BattlerTagType.FLOATING, false, true, 3), new StatusMove(Moves.MAGIC_ROOM, Type.PSYCHIC, -1, 10, -1, 0, 5) .ignoresProtect() .target(MoveTarget.BOTH_SIDES) @@ -8395,7 +8588,7 @@ export function initMoves() { new AttackMove(Moves.SMACK_DOWN, Type.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, 100, 0, 5) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) - .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN ]) + .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ]) .attr(HitsTagAttr, BattlerTagType.FLYING) .makesContact(false), new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5) @@ -8445,7 +8638,7 @@ export function initMoves() { .attr(AfterYouAttr), new AttackMove(Moves.ROUND, Type.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5) .soundBased() - .partial(), + .partial(), // No effect implemented new AttackMove(Moves.ECHOED_VOICE, Type.NORMAL, MoveCategory.SPECIAL, 40, 100, 15, -1, 0, 5) .attr(ConsecutiveUseMultiBasePowerAttr, 5, false) .soundBased(), @@ -8487,7 +8680,8 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true) .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true), new AttackMove(Moves.CIRCLE_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5) - .attr(ForceSwitchOutAttr), + .attr(ForceSwitchOutAttr) + .partial(), // Should force random switches new AttackMove(Moves.INCINERATE, Type.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5) .target(MoveTarget.ALL_NEAR_ENEMIES) .attr(RemoveHeldItemAttr, true), @@ -8555,7 +8749,8 @@ export function initMoves() { .attr(CritOnlyAttr), new AttackMove(Moves.DRAGON_TAIL, Type.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5) .attr(ForceSwitchOutAttr) - .hidesTarget(), + .hidesTarget() + .partial(), // Should force random switches new SelfStatusMove(Moves.WORK_UP, Type.NORMAL, -1, 30, -1, 0, 5) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, true), new AttackMove(Moves.ELECTROWEB, Type.ELECTRIC, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5) @@ -8683,7 +8878,7 @@ export function initMoves() { .ignoresVirtual(), new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6) .attr(AddTypeAttr, Type.GHOST) - .partial(), + .edgeCase(), // Weird interaction with Forest's Curse, reflect type, burn up new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1) .soundBased(), @@ -8696,7 +8891,7 @@ export function initMoves() { .triageMove(), new StatusMove(Moves.FORESTS_CURSE, Type.GRASS, 100, 20, -1, 0, 6) .attr(AddTypeAttr, Type.GRASS) - .partial(), + .edgeCase(), // Weird interaction with Trick or Treat, reflect type, burn up new AttackMove(Moves.PETAL_BLIZZARD, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 6) .windMove() .makesContact(false) @@ -8704,7 +8899,7 @@ export function initMoves() { new AttackMove(Moves.FREEZE_DRY, Type.ICE, MoveCategory.SPECIAL, 70, 100, 20, 10, 0, 6) .attr(StatusEffectAttr, StatusEffect.FREEZE) .attr(WaterSuperEffectTypeMultiplierAttr) - .partial(), // This currently just multiplies the move's power instead of changing its effectiveness. It also doesn't account for abilities that modify type effectiveness such as tera shell. + .edgeCase(), // This currently just multiplies the move's power instead of changing its effectiveness. It also doesn't account for abilities that modify type effectiveness such as tera shell. new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES), @@ -8828,9 +9023,9 @@ export function initMoves() { .attr(NeutralDamageAgainstFlyingTypeMultiplierAttr) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) .attr(HitsTagAttr, BattlerTagType.FLYING) - .attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN) + .attr(HitsTagAttr, BattlerTagType.FLOATING) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) - .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN ]) + .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ]) .makesContact(false) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.THOUSAND_WAVES, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) @@ -9082,15 +9277,15 @@ export function initMoves() { /* Unused */ new AttackMove(Moves.SINISTER_ARROW_RAID, Type.GHOST, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7) .makesContact(false) - .partial() + .edgeCase() // I assume it's because the user needs spirit shackle and decidueye .ignoresVirtual(), new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7) .attr(AlwaysHitMinimizeAttr) .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true) - .partial() + .edgeCase() // I assume it's because it needs darkest lariat and incineroar .ignoresVirtual(), new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) - .partial() + .edgeCase() // I assume it's because it needs sparkling aria and primarina .ignoresVirtual(), new AttackMove(Moves.GUARDIAN_OF_ALOLA, Type.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) .unimplemented() @@ -9099,10 +9294,10 @@ export function initMoves() { .unimplemented() .ignoresVirtual(), new AttackMove(Moves.STOKED_SPARKSURFER, Type.ELECTRIC, MoveCategory.SPECIAL, 175, -1, 1, 100, 0, 7) - .partial() + .edgeCase() // I assume it's because it needs thunderbolt and Alola Raichu .ignoresVirtual(), new AttackMove(Moves.PULVERIZING_PANCAKE, Type.NORMAL, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7) - .partial() + .edgeCase() // I assume it's because it needs giga impact and snorlax .ignoresVirtual(), new SelfStatusMove(Moves.EXTREME_EVOBOOST, Type.NORMAL, -1, 1, -1, 0, 7) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true) @@ -9133,13 +9328,13 @@ export function initMoves() { .attr(RechargeAttr), new AttackMove(Moves.SPECTRAL_THIEF, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 7) .ignoresSubstitute() - .partial(), + .partial(), // Does not steal stats new AttackMove(Moves.SUNSTEEL_STRIKE, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 7) .ignoresAbilities() - .partial(), + .edgeCase(), // Should not ignore abilities when called virtually (metronome) new AttackMove(Moves.MOONGEIST_BEAM, Type.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) .ignoresAbilities() - .partial(), + .edgeCase(), // Should not ignore abilities when called virtually (metronome) new StatusMove(Moves.TEARFUL_LOOK, Type.NORMAL, -1, 20, -1, 0, 7) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1), new AttackMove(Moves.ZING_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7) @@ -9150,7 +9345,7 @@ export function initMoves() { .attr(FormChangeItemTypeAttr), /* Unused */ new AttackMove(Moves.TEN_MILLION_VOLT_THUNDERBOLT, Type.ELECTRIC, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) - .partial() + .edgeCase() // I assume it's because it needs thunderbolt and pikachu in a cap .ignoresVirtual(), /* End Unused */ new AttackMove(Moves.MIND_BLOWN, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 7) @@ -9163,7 +9358,7 @@ export function initMoves() { new AttackMove(Moves.PHOTON_GEYSER, Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) .attr(PhotonGeyserCategoryAttr) .ignoresAbilities() - .partial(), + .edgeCase(), // Should not ignore abilities when called virtually (metronome) /* Unused */ new AttackMove(Moves.LIGHT_THAT_BURNS_THE_SKY, Type.PSYCHIC, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7) .attr(PhotonGeyserCategoryAttr) @@ -9176,7 +9371,7 @@ export function initMoves() { .ignoresAbilities() .ignoresVirtual(), new AttackMove(Moves.LETS_SNUGGLE_FOREVER, Type.FAIRY, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7) - .partial() + .edgeCase() // I assume it needs play rough and mimikyu .ignoresVirtual(), new AttackMove(Moves.SPLINTERED_STORMSHARDS, Type.ROCK, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7) .attr(ClearTerrainAttr) @@ -9186,7 +9381,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true, undefined, undefined, undefined, undefined, true) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES) - .partial() + .edgeCase() // I assume it needs clanging scales and Kommo-O .ignoresVirtual(), /* End Unused */ new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 50, 100, 15, -1, 2, 7) //LGPE Implementation @@ -9249,14 +9444,14 @@ export function initMoves() { new AttackMove(Moves.JAW_LOCK, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8) .attr(JawLockAttr) .bitingMove(), - new SelfStatusMove(Moves.STUFF_CHEEKS, Type.NORMAL, -1, 10, -1, 0, 8) // TODO: Stuff Cheeks should not be selectable when the user does not have a berry, see wiki + new SelfStatusMove(Moves.STUFF_CHEEKS, Type.NORMAL, -1, 10, -1, 0, 8) .attr(EatBerryAttr) .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true) .condition((user) => { const userBerries = user.scene.findModifiers(m => m instanceof BerryModifier, user.isPlayer()); return userBerries.length > 0; }) - .partial(), + .edgeCase(), // Stuff Cheeks should not be selectable when the user does not have a berry, see wiki new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, -1, 0, 8) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true) .attr(AddBattlerTagAttr, BattlerTagType.NO_RETREAT, true, false) @@ -9270,7 +9465,7 @@ export function initMoves() { new AttackMove(Moves.DRAGON_DARTS, Type.DRAGON, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 8) .attr(MultiHitAttr, MultiHitType._2) .makesContact(false) - .partial(), + .partial(), // smart targetting is unimplemented new StatusMove(Moves.TEATIME, Type.NORMAL, -1, 10, -1, 0, 8) .attr(EatBerryAttr) .target(MoveTarget.ALL), @@ -9556,9 +9751,8 @@ export function initMoves() { new AttackMove(Moves.TRIPLE_ARROWS, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 30, 0, 8) .makesContact(false) .attr(HighCritAttr) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1) - .attr(FlinchAttr) - .partial(), + .attr(StatStageChangeAttr, [ Stat.DEF ], -1, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 50) + .attr(FlinchAttr), new AttackMove(Moves.INFERNAL_PARADE, Type.GHOST, MoveCategory.SPECIAL, 60, 100, 15, 30, 0, 8) .attr(StatusEffectAttr, StatusEffect.BURN) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status ? 2 : 1), @@ -9710,7 +9904,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2), new AttackMove(Moves.ORDER_UP, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9) .makesContact(false) - .partial(), + .partial(), // No effect implemented (requires Commander) new AttackMove(Moves.JET_PUNCH, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9) .punchingMove(), new StatusMove(Moves.SPICY_EXTRACT, Type.GRASS, -1, 15, -1, 0, 9) @@ -9922,8 +10116,7 @@ export function initMoves() { new AttackMove(Moves.UPPER_HAND, Type.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9) .attr(FlinchAttr) .condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].priority > 0 ) // TODO: is this bang correct? - //TODO: Should also apply when target move priority increased by ability ex. gale wings - .partial(), + .partial(), // Should also apply when target move priority increased by ability ex. gale wings new AttackMove(Moves.MALIGNANT_CHAIN, Type.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9) .attr(StatusEffectAttr, StatusEffect.TOXIC) ); diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 13e187179d4..f0155b4f2a4 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -44,32 +44,32 @@ export const ATrainersTestEncounter: MysteryEncounter = 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; + 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 @@ -128,6 +128,7 @@ export const ATrainersTestEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -149,11 +150,11 @@ export const ATrainersTestEncounter: MysteryEncounter = pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: encounter.misc.trainerEggDescription, - tier: EggTier.ULTRA + tier: EggTier.EPIC }; encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`)); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]); - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); } ) .withSimpleOption( @@ -171,7 +172,7 @@ export const ATrainersTestEncounter: MysteryEncounter = pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: encounter.misc.trainerEggDescription, - tier: EggTier.GREAT + tier: EggTier.RARE }; encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`)); setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]); diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index c98947a3f93..c53b802bb22 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -166,6 +166,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -285,7 +286,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = ignorePp: true }); - transitionMysteryEncounterIntroVisuals(scene, true, true, 500); + await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); }) .build() @@ -327,7 +328,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = }); await scene.updateModifiers(true); - transitionMysteryEncounterIntroVisuals(scene, true, true, 500); + await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); leaveEncounterWithoutBattle(scene, true); }) .build() @@ -358,7 +359,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ]; greedent.passive = true; - transitionMysteryEncounterIntroVisuals(scene, true, true, 500); + await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); await catchPokemon(scene, greedent, null, PokeballType.POKEBALL, false); leaveEncounterWithoutBattle(scene, true); }) diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index e445a8f481d..8dc4eca25bf 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -64,6 +64,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = speaker: `${namespace}:speaker`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -132,8 +133,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .withPrimaryPokemonRequirement(new CombinationPokemonRequirement( - new MoveRequirement(EXTORTION_MOVES), - new AbilityRequirement(EXTORTION_ABILITIES)) + new MoveRequirement(EXTORTION_MOVES, true), + new AbilityRequirement(EXTORTION_ABILITIES, true)) ) .withDialogue({ buttonLabel: `${namespace}:option.2.label`, diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 3e5d75727b1..095f8a8473b 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -110,6 +110,7 @@ export const BerriesAboundEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index 20c0569c725..d316ab14cde 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -42,6 +42,8 @@ import { AttackTypeBoosterModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, + GigantamaxAccessModifier, + MegaEvolutionAccessModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import i18next from "i18next"; @@ -276,6 +278,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -355,10 +358,17 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = }, ]; } else { - // If player has any evolution/form change items that are valid for their party, will spawn one of those items in addition to a Master Ball - const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)!, generateModifierTypeOption(scene, modifierTypes.MAX_LURE)! ]; + // If the player has any evolution/form change items that are valid for their party, + // spawn one of those items in addition to Dynamax Band, Mega Band, and Master Ball + const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)! ]; const specialOptions: ModifierTypeOption[] = []; + if (!scene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) { + modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.MEGA_BRACELET)!); + } + if (!scene.findModifier(m => m instanceof GigantamaxAccessModifier)) { + modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.DYNAMAX_BAND)!); + } const nonRareEvolutionModifier = generateModifierTypeOption(scene, modifierTypes.EVOLUTION_ITEM); if (nonRareEvolutionModifier) { specialOptions.push(nonRareEvolutionModifier); diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 6c028d4619a..57c8aa7a561 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -28,7 +28,7 @@ import { BattlerIndex } from "#app/battle"; import { Moves } from "#enums/moves"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import { MoveCategory } from "#app/data/move"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { EncounterAnim } from "#enums/encounter-anims"; import { Challenges } from "#enums/challenges"; @@ -133,7 +133,7 @@ export const ClowningAroundEncounter: MysteryEncounter = }, { // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter species: getPokemonSpecies(Species.BLACEPHALON), - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ ability: ability, types: [ randSeedInt(18), randSeedInt(18) ]}), + customPokemonData: new CustomPokemonData({ ability: ability, types: [ randSeedInt(18), randSeedInt(18) ]}), isBoss: true, moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ] }, @@ -148,6 +148,7 @@ export const ClowningAroundEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -352,15 +353,15 @@ export const ClowningAroundEncounter: MysteryEncounter = newTypes.push(secondType); // Apply the type changes (to both base and fusion, if pokemon is fused) - if (!pokemon.mysteryEncounterPokemonData) { - pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.customPokemonData) { + pokemon.customPokemonData = new CustomPokemonData(); } - pokemon.mysteryEncounterPokemonData.types = newTypes; + pokemon.customPokemonData.types = newTypes; if (pokemon.isFusion()) { - if (!pokemon.fusionMysteryEncounterPokemonData) { - pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.fusionCustomPokemonData) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(); } - pokemon.fusionMysteryEncounterPokemonData.types = newTypes; + pokemon.fusionCustomPokemonData.types = newTypes; } } }) @@ -425,15 +426,15 @@ function onYesAbilitySwap(scene: BattleScene, resolve) { // Do ability swap const encounter = scene.currentBattle.mysteryEncounter!; if (pokemon.isFusion()) { - if (!pokemon.fusionMysteryEncounterPokemonData) { - pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.fusionCustomPokemonData) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(); } - pokemon.fusionMysteryEncounterPokemonData.ability = encounter.misc.ability; + pokemon.fusionCustomPokemonData.ability = encounter.misc.ability; } else { - if (!pokemon.mysteryEncounterPokemonData) { - pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.customPokemonData) { + pokemon.customPokemonData = new CustomPokemonData(); } - pokemon.mysteryEncounterPokemonData.ability = encounter.misc.ability; + pokemon.customPokemonData.ability = encounter.misc.ability; } encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender()); scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true)); diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index cb07bf06a81..55d7ce0e92d 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -102,6 +102,7 @@ export const DancingLessonsEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -227,7 +228,7 @@ export const DancingLessonsEncounter: MysteryEncounter = }) .withOptionPhase(async (scene: BattleScene) => { // Learn its Dance - hideOricorioPokemon(scene); + await hideOricorioPokemon(scene); leaveEncounterWithoutBattle(scene, true); }) .build() @@ -235,7 +236,7 @@ export const DancingLessonsEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically + .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`, @@ -302,7 +303,7 @@ export const DancingLessonsEncounter: MysteryEncounter = } } - hideOricorioPokemon(scene); + await hideOricorioPokemon(scene); await catchPokemon(scene, oricorio, null, PokeballType.POKEBALL, false); leaveEncounterWithoutBattle(scene, true); }) diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index fc8c8088d58..7f199b5487c 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -117,6 +117,7 @@ export const DarkDealEncounter: MysteryEncounter = .withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party .withCatchAllowed(true) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -181,7 +182,7 @@ export const DarkDealEncounter: MysteryEncounter = const config: EnemyPartyConfig = { pokemonConfigs: [ pokemonConfig ], }; - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .build() ) diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index a11dc8cbe72..5686d0f6ce5 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -84,6 +84,7 @@ export const DelibirdyEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index 1505768f968..10034d19263 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -51,6 +51,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = }, ]) .withAutoHideIntroVisuals(false) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index a75e5ef6a77..bf5fb28163b 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -52,6 +52,7 @@ export const FieldTripEncounter: MysteryEncounter = }, ]) .withAutoHideIntroVisuals(false) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 9e7652e24ea..d306206159a 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -122,6 +122,7 @@ export const FieryFalloutEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -221,12 +222,13 @@ export const FieryFalloutEncounter: MysteryEncounter = ], }) .withPreOptionPhase(async (scene: BattleScene) => { + // Do NOT await this, to prevent player from repeatedly pressing options transitionMysteryEncounterIntroVisuals(scene, false, false, 2000); }) .withOptionPhase(async (scene: BattleScene) => { // Fire types help calm the Volcarona const encounter = scene.currentBattle.mysteryEncounter!; - transitionMysteryEncounterIntroVisuals(scene); + await transitionMysteryEncounterIntroVisuals(scene); setEncounterRewards(scene, { fillRemaining: true }, undefined, diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index a04521839fe..889868519d2 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -120,6 +120,7 @@ export const FightOrFlightEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -144,7 +145,7 @@ export const FightOrFlightEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .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`, diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 2b103e0a293..b843a929c08 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -76,6 +76,7 @@ export const FunAndGamesEncounter: MysteryEncounter = text: `${namespace}:intro_dialogue`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -151,7 +152,7 @@ export const FunAndGamesEncounter: MysteryEncounter = }, async (scene: BattleScene) => { // Leave encounter with no rewards or exp - transitionMysteryEncounterIntroVisuals(scene, true, true); + await transitionMysteryEncounterIntroVisuals(scene, true, true); leaveEncounterWithoutBattle(scene, true); return true; } diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 7b929ea5e7b..376bdf0c95d 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -96,6 +96,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -398,7 +399,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = if (modifier.stackCount === 0) { scene.removeModifier(modifier); } - scene.updateModifiers(true, true); + await scene.updateModifiers(true, true); // Generate a trainer name const traderName = generateRandomTraderName(); diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index 6ca131543b4..8e7ea52a967 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -50,6 +50,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -128,7 +129,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with * * @param scene Battle scene */ -async function handlePokemonGuidingYouPhase(scene: BattleScene) { +function handlePokemonGuidingYouPhase(scene: BattleScene) { const laprasSpecies = getPokemonSpecies(Species.LAPRAS); const { mysteryEncounter } = scene.currentBattle; diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index 08536f44245..f282064bb94 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -125,6 +125,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -146,11 +147,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter = setEncounterRewards(scene, { 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 ret; + let initBattlePromise: Promise; scene.executeWithSeedOffset(() => { - ret = initBattleWithEnemyConfig(scene, config); + initBattlePromise = initBattleWithEnemyConfig(scene, config); }, scene.currentBattle.waveIndex * 10); - return ret; + await initBattlePromise!; } ) .withSimpleOption( @@ -171,11 +172,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter = setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: true }); // Seed offsets to remove possibility of different trainers having exact same teams - let ret; + let initBattlePromise: Promise; scene.executeWithSeedOffset(() => { - ret = initBattleWithEnemyConfig(scene, config); + initBattlePromise = initBattleWithEnemyConfig(scene, config); }, scene.currentBattle.waveIndex * 100); - return ret; + await initBattlePromise!; } ) .withSimpleOption( @@ -199,11 +200,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter = setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true }); // Seed offsets to remove possibility of different trainers having exact same teams - let ret; + let initBattlePromise: Promise; scene.executeWithSeedOffset(() => { - ret = initBattleWithEnemyConfig(scene, config); + initBattlePromise = initBattleWithEnemyConfig(scene, config); }, scene.currentBattle.waveIndex * 1000); - return ret; + await initBattlePromise!; } ) .withOutroDialogue([ diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 2b44f6ee33d..693d935ae17 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -61,6 +61,7 @@ export const MysteriousChestEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -183,7 +184,7 @@ export const MysteriousChestEncounter: MysteryEncounter = scene.unshiftPhase(new GameOverPhase(scene)); } else { // Show which Pokemon was KOed, then start battle against Gimmighoul - transitionMysteryEncounterIntroVisuals(scene, true, true, 500); + await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); setEncounterRewards(scene, { fillRemaining: true }); await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); } diff --git a/src/data/mystery-encounters/encounters/part-timer-encounter.ts b/src/data/mystery-encounters/encounters/part-timer-encounter.ts index 2f41aa96677..092d2ab2673 100644 --- a/src/data/mystery-encounters/encounters/part-timer-encounter.ts +++ b/src/data/mystery-encounters/encounters/part-timer-encounter.ts @@ -69,6 +69,7 @@ export const PartTimerEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -226,7 +227,7 @@ export const PartTimerEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically + .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index d029460e617..0ee3c57b0a2 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -54,6 +54,7 @@ export const SafariZoneEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -302,13 +303,16 @@ async function summonSafariPokemon(scene: BattleScene) { scene.unshiftPhase(new SummonPhase(scene, 0, false)); encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon)); - showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared") ?? "", null, 1500, false) - .then(() => { - const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier); - if (ivScannerModifier) { - scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6))); - } - }); + + // TODO: If we await showEncounterText here, then the text will display without + // the wild Pokemon on screen, but if we don't await it, then the text never + // shows up and the IV scanner breaks. For now, we place the IV scanner code + // separately so that at least the IV scanner works. + + const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier); + if (ivScannerModifier) { + scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6))); + } } function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise { diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 89cb572962c..d30c97b27de 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -62,6 +62,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = speaker: `${namespace}:speaker`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -137,11 +138,11 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = newNature = randSeedInt(25) as Nature; } - chosenPokemon.nature = newNature; + chosenPokemon.customPokemonData.nature = newNature; encounter.setDialogueToken("newNature", getNatureName(newNature)); queueEncounterMessage(scene, `${namespace}:cheap_side_effects`); setEncounterExp(scene, [ chosenPokemon.id ], 100); - chosenPokemon.updateInfo(); + await chosenPokemon.updateInfo(); }) .build() ) @@ -203,7 +204,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = queueEncounterMessage(scene, `${namespace}:no_bad_effects`); setEncounterExp(scene, [ chosenPokemon.id ], 100); - chosenPokemon.updateInfo(); + await chosenPokemon.updateInfo(); }) .build() ) diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index d0b4cc13301..8ea19e1225b 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -18,7 +18,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { BerryType } from "#enums/berry-type"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; /** i18n namespace for the encounter */ const namespace = "mysteryEncounters/slumberingSnorlax"; @@ -72,7 +72,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = stackCount: 2 }, ], - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), + customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep }; const config: EnemyPartyConfig = { @@ -88,6 +88,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -142,7 +143,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) + .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index 63ab178c52a..e8f11f02e18 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -58,6 +58,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -148,7 +149,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter = const magnet = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.STEEL ])!; const metalCoat = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.ELECTRIC ])!; setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ magnet, metalCoat ], fillRemaining: true }); - transitionMysteryEncounterIntroVisuals(scene, true, true); + await transitionMysteryEncounterIntroVisuals(scene, true, true); await initBattleWithEnemyConfig(scene, config); } ) diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index aca46f1598b..9c10d33d019 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -25,6 +25,7 @@ import { achvs } from "#app/system/achv"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { Type } from "#app/data/type"; import { getPokeballTintColor } from "#app/data/pokeball"; +import { PokemonHeldItemModifier } from "#app/modifier/modifier"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theExpertPokemonBreeder"; @@ -61,7 +62,7 @@ const POOL_1_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.JYNX ], + [ Species.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE) ], [ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE) ], [ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE) ], [ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE) ], @@ -163,7 +164,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = 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("pokemon1CommonEggs", eggsText); + encounter.setDialogueToken("pokemon2CommonEggs", eggsText); } encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip; @@ -196,6 +197,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -220,7 +222,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon1; encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1); @@ -244,10 +246,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = } encounter.onGameOver = onGameOver; - initBattleWithEnemyConfig(scene, config); - }) - .withPostOptionPhase(async (scene: BattleScene) => { - await doPostEncounterCleanup(scene); + await initBattleWithEnemyConfig(scene, config); }) .build() ) @@ -272,7 +271,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon2; encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2); @@ -296,10 +295,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = } encounter.onGameOver = onGameOver; - initBattleWithEnemyConfig(scene, config); - }) - .withPostOptionPhase(async (scene: BattleScene) => { - await doPostEncounterCleanup(scene); + await initBattleWithEnemyConfig(scene, config); }) .build() ) @@ -324,7 +320,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon3; encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3); @@ -348,10 +344,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = } encounter.onGameOver = onGameOver; - initBattleWithEnemyConfig(scene, config); - }) - .withPostOptionPhase(async (scene: BattleScene) => { - await doPostEncounterCleanup(scene); + await initBattleWithEnemyConfig(scene, config); }) .build() ) @@ -493,7 +486,7 @@ function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number) pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: eggDescription, - tier: EggTier.GREAT + tier: EggTier.RARE }); } } @@ -520,19 +513,19 @@ function checkAchievement(scene: BattleScene) { } } -async function restorePartyAndHeldItems(scene: BattleScene) { +function restorePartyAndHeldItems(scene: BattleScene) { const encounter = scene.currentBattle.mysteryEncounter!; // Restore original party scene.getParty().push(...encounter.misc.originalParty); // Restore held items const originalHeldItems = encounter.misc.originalPartyHeldItems; - originalHeldItems.forEach(pokemonHeldItemsList => { + originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => { pokemonHeldItemsList.forEach(heldItem => { scene.addModifier(heldItem, true, false, false, true); }); }); - await scene.updateModifiers(true); + scene.updateModifiers(true); } function onGameOver(scene: BattleScene) { @@ -608,13 +601,13 @@ function onGameOver(scene: BattleScene) { return false; } -async function doPostEncounterCleanup(scene: BattleScene) { +function doPostEncounterCleanup(scene: BattleScene) { const encounter = scene.currentBattle.mysteryEncounter!; if (!encounter.misc.encounterFailed) { // Give achievement if in Space biome checkAchievement(scene); // Give 20 friendship to the chosen pokemon encounter.misc.chosenPokemon.addFriendship(FRIENDSHIP_ADDED); - await restorePartyAndHeldItems(scene); + restorePartyAndHeldItems(scene); } } diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index 2720653e654..95f359547e4 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -53,6 +53,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = speaker: `${namespace}:speaker`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index d1d5b484129..397d2af9522 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -14,7 +14,7 @@ import { BattlerIndex } from "#app/battle"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { Stat } from "#enums/stat"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; @@ -79,7 +79,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = species: getPokemonSpecies(Species.SHUCKLE), isBoss: true, bossSegments: 5, - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), + customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.BOLD, moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ], modifierConfigs: [ @@ -117,6 +117,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -200,7 +201,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = }); encounter.dialogue.outro = []; - transitionMysteryEncounterIntroVisuals(scene, true, true, 500); + await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); } ) diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index ad4f2dd8498..bf322802f81 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -94,6 +94,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -110,8 +111,8 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = }, async (scene: BattleScene) => { // Spawn 5 trainer battles back to back with Macho Brace in rewards - scene.currentBattle.mysteryEncounter!.doContinueEncounter = (scene: BattleScene) => { - return endTrainerBattleAndShowDialogue(scene); + scene.currentBattle.mysteryEncounter!.doContinueEncounter = async (scene: BattleScene) => { + await endTrainerBattleAndShowDialogue(scene); }; await transitionMysteryEncounterIntroVisuals(scene, true, false); await spawnNextTrainerOrEndEncounter(scene); diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index ff993f339cb..9f80bbbffde 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -52,6 +52,7 @@ export const TrainingSessionEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -161,7 +162,7 @@ export const TrainingSessionEncounter: MysteryEncounter = setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .build() ) @@ -237,7 +238,7 @@ export const TrainingSessionEncounter: MysteryEncounter = setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .build() ) @@ -350,7 +351,7 @@ export const TrainingSessionEncounter: MysteryEncounter = setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .build() ) diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index be2cd796386..2b3b38b2164 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -54,6 +54,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -104,7 +105,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = }) .withOptionPhase(async (scene: BattleScene) => { // Gain 2 Leftovers and 2 Shell Bell - transitionMysteryEncounterIntroVisuals(scene); + await transitionMysteryEncounterIntroVisuals(scene); await tryApplyDigRewardItems(scene); const blackSludge = generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [ SHOP_ITEM_COST_MULTIPLIER ]); @@ -135,7 +136,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = // Investigate garbage, battle Gmax Garbodor scene.setFieldScale(0.75); await showEncounterText(scene, `${namespace}:option.2.selected_2`); - transitionMysteryEncounterIntroVisuals(scene); + await transitionMysteryEncounterIntroVisuals(scene); const encounter = scene.currentBattle.mysteryEncounter!; @@ -221,7 +222,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) { await showEncounterText(scene, i18next.t("battle:rewardGainCount", { modifierName: shellBell.name, count: 2 }), null, undefined, true); } -async function doGarbageDig(scene: BattleScene) { +function doGarbageDig(scene: BattleScene) { scene.playSound("battle_anims/PRSFX- Dig2"); scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => { scene.playSound("battle_anims/PRSFX- Dig2"); diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 51c1d5f963f..b7ce3bab48c 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -125,6 +125,7 @@ export const UncommonBreedEncounter: MysteryEncounter = scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2")); return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -209,7 +210,7 @@ export const UncommonBreedEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 17f33c27645..b97a22dbe51 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -12,7 +12,7 @@ import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from " import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { achvs } from "#app/system/achv"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; @@ -125,6 +125,7 @@ export const WeirdDreamEncounter: MysteryEncounter = text: `${namespace}:intro_dialogue`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) @@ -378,10 +379,10 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon newType = randSeedInt(18) as Type; } newTypes.push(newType); - if (!newPokemon.mysteryEncounterPokemonData) { - newPokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!newPokemon.customPokemonData) { + newPokemon.customPokemonData = new CustomPokemonData(); } - newPokemon.mysteryEncounterPokemonData.types = newTypes; + newPokemon.customPokemonData.types = newTypes; for (const item of transformation.heldItems) { item.pokemonId = newPokemon.id; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 86e04e80807..a57cedc8fa3 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -15,6 +15,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import { SpeciesFormKey } from "#enums/species-form-key"; +import { allAbilities } from "#app/data/ability"; export interface EncounterRequirement { meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met @@ -476,9 +477,11 @@ export class MoveRequirement extends EncounterPokemonRequirement { requiredMoves: Moves[] = []; minNumberOfPokemon: number; invertQuery: boolean; + excludeDisallowedPokemon: boolean; - constructor(moves: Moves | Moves[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { + constructor(moves: Moves | Moves[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) { super(); + this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; this.requiredMoves = Array.isArray(moves) ? moves : [ moves ]; @@ -494,10 +497,15 @@ export class MoveRequirement extends EncounterPokemonRequirement { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { if (!this.invertQuery) { - return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length > 0).length > 0); + // get the Pokemon with at least one move in the required moves list + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId))); } else { // for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves - return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length === 0).length === 0); + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && !pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId))); } } @@ -559,9 +567,11 @@ export class AbilityRequirement extends EncounterPokemonRequirement { requiredAbilities: Abilities[]; minNumberOfPokemon: number; invertQuery: boolean; + excludeDisallowedPokemon: boolean; - constructor(abilities: Abilities | Abilities[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { + constructor(abilities: Abilities | Abilities[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) { super(); + this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; this.requiredAbilities = Array.isArray(abilities) ? abilities : [ abilities ]; @@ -577,16 +587,21 @@ export class AbilityRequirement extends EncounterPokemonRequirement { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { if (!this.invertQuery) { - return partyPokemon.filter((pokemon) => this.requiredAbilities.some((ability) => pokemon.getAbility().id === ability)); + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && this.requiredAbilities.some((ability) => pokemon.hasAbility(ability, false))); } else { - // for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilitiess - return partyPokemon.filter((pokemon) => this.requiredAbilities.filter((ability) => pokemon.getAbility().id === ability).length === 0); + // for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilities + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && this.requiredAbilities.filter((ability) => pokemon.hasAbility(ability, false)).length === 0); } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - if (pokemon?.getAbility().id && this.requiredAbilities.some(a => pokemon.getAbility().id === a)) { - return [ "ability", pokemon.getAbility().name ]; + override getDialogueToken(_scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + const matchingAbility = this.requiredAbilities.find(a => pokemon?.hasAbility(a, false)); + if (!isNullOrUndefined(matchingAbility)) { + return [ "ability", allAbilities[matchingAbility].name ]; } return [ "ability", "" ]; } diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index aabb0a3311a..ee9eb159e10 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -190,7 +190,7 @@ export default class MysteryEncounter implements IMysteryEncounter { secondaryPokemon?: PlayerPokemon[]; // #region Post-construct / Auto-populated params - + localizationKey: string; /** * Dialogue object containing all the dialogue, messages, tooltips, etc. for an encounter */ @@ -264,6 +264,7 @@ export default class MysteryEncounter implements IMysteryEncounter { Object.assign(this, encounter); } this.encounterTier = this.encounterTier ?? MysteryEncounterTier.COMMON; + this.localizationKey = this.localizationKey ?? ""; this.dialogue = this.dialogue ?? {}; this.spriteConfigs = this.spriteConfigs ? [ ...this.spriteConfigs ] : []; // Default max is 1 for ROGUE encounters, 2 for others @@ -324,7 +325,7 @@ export default class MysteryEncounter implements IMysteryEncounter { if (activeMon.length > 0) { this.primaryPokemon = activeMon[0]; } else { - this.primaryPokemon = scene.getParty().filter(p => !p.isFainted())[0]; + this.primaryPokemon = scene.getParty().filter(p => p.isAllowedInBattle())[0]; } return true; } @@ -528,6 +529,7 @@ export class MysteryEncounterBuilder implements Partial { options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]]; enemyPartyConfigs: EnemyPartyConfig[] = []; + localizationKey: string = ""; dialogue: MysteryEncounterDialogue = {}; requirements: EncounterSceneRequirement[] = []; primaryPokemonRequirements: EncounterPokemonRequirement[] = []; @@ -632,6 +634,16 @@ export class MysteryEncounterBuilder implements Partial { return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue); } + /** + * Sets the localization key used by the encounter + * @param localizationKey the string used as the key + * @returns `this` + */ + setLocalizationKey(localizationKey: string): this { + this.localizationKey = localizationKey; + return this; + } + /** * OPTIONAL */ diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index f76dff1fec7..5cd2fbffd5f 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -27,7 +27,7 @@ import { Status, StatusEffect } from "#app/data/status-effect"; import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-config"; import PokemonSpecies from "#app/data/pokemon-species"; import { Egg, IEggOptions } from "#app/data/egg"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import HeldModifierConfig from "#app/interfaces/held-modifier-config"; import { MovePhase } from "#app/phases/move-phase"; import { EggLapsePhase } from "#app/phases/egg-lapse-phase"; @@ -71,7 +71,7 @@ export interface EnemyPokemonConfig { nickname?: string; bossSegments?: number; bossSegmentModifier?: number; // Additive to the determined segment number - mysteryEncounterPokemonData?: MysteryEncounterPokemonData; + customPokemonData?: CustomPokemonData; formIndex?: number; abilityIndex?: number; level?: number; @@ -145,7 +145,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: newTrainer.setVisible(false); scene.field.add(newTrainer); scene.currentBattle.trainer = newTrainer; - loadEnemyAssets.push(newTrainer.loadAssets()); + loadEnemyAssets.push(newTrainer.loadAssets().then(() => newTrainer.initSprite())); battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex); } else { @@ -250,8 +250,8 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: } // Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.) - if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) { - enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData; + if (!isNullOrUndefined(config.customPokemonData)) { + enemyPokemon.customPokemonData = config.customPokemonData; } // Set Boss @@ -1069,19 +1069,19 @@ export function calculateRareSpawnAggregateStats(scene: BattleScene, luckValue: const tier = tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; switch (tier) { - default: - case BiomePoolTier.BOSS: - ++bossEncountersByRarity[0]; - break; - case BiomePoolTier.BOSS_RARE: - ++bossEncountersByRarity[1]; - break; - case BiomePoolTier.BOSS_SUPER_RARE: - ++bossEncountersByRarity[2]; - break; - case BiomePoolTier.BOSS_ULTRA_RARE: - ++bossEncountersByRarity[3]; - break; + default: + case BiomePoolTier.BOSS: + ++bossEncountersByRarity[0]; + break; + case BiomePoolTier.BOSS_RARE: + ++bossEncountersByRarity[1]; + break; + case BiomePoolTier.BOSS_SUPER_RARE: + ++bossEncountersByRarity[2]; + break; + case BiomePoolTier.BOSS_ULTRA_RARE: + ++bossEncountersByRarity[3]; + break; } } diff --git a/src/data/nature.ts b/src/data/nature.ts index c614be465c3..edac06f1a4f 100644 --- a/src/data/nature.ts +++ b/src/data/nature.ts @@ -37,76 +37,76 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals export function getNatureStatMultiplier(nature: Nature, stat: Stat): number { switch (stat) { - case Stat.ATK: - switch (nature) { - case Nature.LONELY: - case Nature.BRAVE: - case Nature.ADAMANT: - case Nature.NAUGHTY: - return 1.1; - case Nature.BOLD: - case Nature.TIMID: - case Nature.MODEST: - case Nature.CALM: - return 0.9; - } - break; - case Stat.DEF: - switch (nature) { - case Nature.BOLD: - case Nature.RELAXED: - case Nature.IMPISH: - case Nature.LAX: - return 1.1; - case Nature.LONELY: - case Nature.HASTY: - case Nature.MILD: - case Nature.GENTLE: - return 0.9; - } - break; - case Stat.SPATK: - switch (nature) { - case Nature.MODEST: - case Nature.MILD: - case Nature.QUIET: - case Nature.RASH: - return 1.1; - case Nature.ADAMANT: - case Nature.IMPISH: - case Nature.JOLLY: - case Nature.CAREFUL: - return 0.9; - } - break; - case Stat.SPDEF: - switch (nature) { - case Nature.CALM: - case Nature.GENTLE: - case Nature.SASSY: - case Nature.CAREFUL: - return 1.1; - case Nature.NAUGHTY: - case Nature.LAX: - case Nature.NAIVE: - case Nature.RASH: - return 0.9; - } - break; - case Stat.SPD: - switch (nature) { - case Nature.TIMID: - case Nature.HASTY: - case Nature.JOLLY: - case Nature.NAIVE: - return 1.1; - case Nature.BRAVE: - case Nature.RELAXED: - case Nature.QUIET: - case Nature.SASSY: - return 0.9; - } - break; + case Stat.ATK: + switch (nature) { + case Nature.LONELY: + case Nature.BRAVE: + case Nature.ADAMANT: + case Nature.NAUGHTY: + return 1.1; + case Nature.BOLD: + case Nature.TIMID: + case Nature.MODEST: + case Nature.CALM: + return 0.9; + } + break; + case Stat.DEF: + switch (nature) { + case Nature.BOLD: + case Nature.RELAXED: + case Nature.IMPISH: + case Nature.LAX: + return 1.1; + case Nature.LONELY: + case Nature.HASTY: + case Nature.MILD: + case Nature.GENTLE: + return 0.9; + } + break; + case Stat.SPATK: + switch (nature) { + case Nature.MODEST: + case Nature.MILD: + case Nature.QUIET: + case Nature.RASH: + return 1.1; + case Nature.ADAMANT: + case Nature.IMPISH: + case Nature.JOLLY: + case Nature.CAREFUL: + return 0.9; + } + break; + case Stat.SPDEF: + switch (nature) { + case Nature.CALM: + case Nature.GENTLE: + case Nature.SASSY: + case Nature.CAREFUL: + return 1.1; + case Nature.NAUGHTY: + case Nature.LAX: + case Nature.NAIVE: + case Nature.RASH: + return 0.9; + } + break; + case Stat.SPD: + switch (nature) { + case Nature.TIMID: + case Nature.HASTY: + case Nature.JOLLY: + case Nature.NAIVE: + return 1.1; + case Nature.BRAVE: + case Nature.RELAXED: + case Nature.QUIET: + case Nature.SASSY: + return 0.9; + } + break; } return 1; diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index 59ff4ed86ce..57a78e2cd61 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -8,77 +8,77 @@ export const MAX_PER_TYPE_POKEBALLS: integer = 99; export function getPokeballAtlasKey(type: PokeballType): string { switch (type) { - case PokeballType.POKEBALL: - return "pb"; - case PokeballType.GREAT_BALL: - return "gb"; - case PokeballType.ULTRA_BALL: - return "ub"; - case PokeballType.ROGUE_BALL: - return "rb"; - case PokeballType.MASTER_BALL: - return "mb"; - case PokeballType.LUXURY_BALL: - return "lb"; + case PokeballType.POKEBALL: + return "pb"; + case PokeballType.GREAT_BALL: + return "gb"; + case PokeballType.ULTRA_BALL: + return "ub"; + case PokeballType.ROGUE_BALL: + return "rb"; + case PokeballType.MASTER_BALL: + return "mb"; + case PokeballType.LUXURY_BALL: + return "lb"; } } export function getPokeballName(type: PokeballType): string { let ret: string; switch (type) { - case PokeballType.POKEBALL: - ret = i18next.t("pokeball:pokeBall"); - break; - case PokeballType.GREAT_BALL: - ret = i18next.t("pokeball:greatBall"); - break; - case PokeballType.ULTRA_BALL: - ret = i18next.t("pokeball:ultraBall"); - break; - case PokeballType.ROGUE_BALL: - ret = i18next.t("pokeball:rogueBall"); - break; - case PokeballType.MASTER_BALL: - ret = i18next.t("pokeball:masterBall"); - break; - case PokeballType.LUXURY_BALL: - ret = i18next.t("pokeball:luxuryBall"); - break; + case PokeballType.POKEBALL: + ret = i18next.t("pokeball:pokeBall"); + break; + case PokeballType.GREAT_BALL: + ret = i18next.t("pokeball:greatBall"); + break; + case PokeballType.ULTRA_BALL: + ret = i18next.t("pokeball:ultraBall"); + break; + case PokeballType.ROGUE_BALL: + ret = i18next.t("pokeball:rogueBall"); + break; + case PokeballType.MASTER_BALL: + ret = i18next.t("pokeball:masterBall"); + break; + case PokeballType.LUXURY_BALL: + ret = i18next.t("pokeball:luxuryBall"); + break; } return ret; } export function getPokeballCatchMultiplier(type: PokeballType): number { switch (type) { - case PokeballType.POKEBALL: - return 1; - case PokeballType.GREAT_BALL: - return 1.5; - case PokeballType.ULTRA_BALL: - return 2; - case PokeballType.ROGUE_BALL: - return 3; - case PokeballType.MASTER_BALL: - return -1; - case PokeballType.LUXURY_BALL: - return 1; + case PokeballType.POKEBALL: + return 1; + case PokeballType.GREAT_BALL: + return 1.5; + case PokeballType.ULTRA_BALL: + return 2; + case PokeballType.ROGUE_BALL: + return 3; + case PokeballType.MASTER_BALL: + return -1; + case PokeballType.LUXURY_BALL: + return 1; } } export function getPokeballTintColor(type: PokeballType): number { switch (type) { - case PokeballType.POKEBALL: - return 0xd52929; - case PokeballType.GREAT_BALL: - return 0x94b4de; - case PokeballType.ULTRA_BALL: - return 0xe6cd31; - case PokeballType.ROGUE_BALL: - return 0xd52929; - case PokeballType.MASTER_BALL: - return 0xa441bd; - case PokeballType.LUXURY_BALL: - return 0xffde6a; + case PokeballType.POKEBALL: + return 0xd52929; + case PokeballType.GREAT_BALL: + return 0x94b4de; + case PokeballType.ULTRA_BALL: + return 0xe6cd31; + case PokeballType.ROGUE_BALL: + return 0xd52929; + case PokeballType.MASTER_BALL: + return 0xa441bd; + case PokeballType.LUXURY_BALL: + return 0xffde6a; } } diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 03b6b89e5b1..7cc20d50fb9 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -799,8 +799,8 @@ export const pokemonFormChanges: PokemonFormChanges = { [Species.ZYGARDE]: [ new SpeciesFormChange(Species.ZYGARDE, "50-pc", "complete", new SpeciesFormChangeManualTrigger(), true), new SpeciesFormChange(Species.ZYGARDE, "complete", "50-pc", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.ZYGARDE, "10-pc", "complete", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.ZYGARDE, "complete", "10-pc", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.ZYGARDE, "10-pc", "10-complete", new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.ZYGARDE, "10-complete", "10-pc", new SpeciesFormChangeManualTrigger(), true) ], [Species.DIANCIE]: [ new SpeciesFormChange(Species.DIANCIE, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.DIANCITE)) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 1ceb5971f6a..947ac939989 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -238,8 +238,8 @@ export abstract class PokemonSpeciesForm { isRareRegional(): boolean { switch (this.getRegion()) { - case Region.HISUI: - return true; + case Region.HISUI: + return true; } return false; @@ -265,14 +265,14 @@ export abstract class PokemonSpeciesForm { getBaseExp(): number { let ret = this.baseExp; switch (this.getFormSpriteKey()) { - case SpeciesFormKey.MEGA: - case SpeciesFormKey.MEGA_X: - case SpeciesFormKey.MEGA_Y: - case SpeciesFormKey.PRIMAL: - case SpeciesFormKey.GIGANTAMAX: - case SpeciesFormKey.ETERNAMAX: - ret *= 1.5; - break; + case SpeciesFormKey.MEGA: + case SpeciesFormKey.MEGA_X: + case SpeciesFormKey.MEGA_Y: + case SpeciesFormKey.PRIMAL: + case SpeciesFormKey.GIGANTAMAX: + case SpeciesFormKey.ETERNAMAX: + ret *= 1.5; + break; } return ret; } @@ -346,29 +346,29 @@ export abstract class PokemonSpeciesForm { } switch (this.speciesId) { - case Species.HIPPOPOTAS: - case Species.HIPPOWDON: - case Species.UNFEZANT: - case Species.FRILLISH: - case Species.JELLICENT: - case Species.PYROAR: - ret += female ? "-f" : ""; - break; + case Species.HIPPOPOTAS: + case Species.HIPPOWDON: + case Species.UNFEZANT: + case Species.FRILLISH: + case Species.JELLICENT: + case Species.PYROAR: + ret += female ? "-f" : ""; + break; } let formSpriteKey = this.getFormSpriteKey(formIndex); if (formSpriteKey) { switch (this.speciesId) { - case Species.DUDUNSPARCE: - break; - case Species.ZACIAN: - case Species.ZAMAZENTA: - if (formSpriteKey.startsWith("behemoth")) { - formSpriteKey = "crowned"; - } - default: - ret += `-${formSpriteKey}`; - break; + case Species.DUDUNSPARCE: + break; + case Species.ZACIAN: + case Species.ZAMAZENTA: + if (formSpriteKey.startsWith("behemoth")) { + formSpriteKey = "crowned"; + } + default: + ret += `-${formSpriteKey}`; + break; } } @@ -383,15 +383,15 @@ export abstract class PokemonSpeciesForm { let speciesId = this.speciesId; if (this.speciesId > 2000) { switch (this.speciesId) { - case Species.GALAR_SLOWPOKE: - break; - case Species.ETERNAL_FLOETTE: - break; - case Species.BLOODMOON_URSALUNA: - break; - default: - speciesId = speciesId % 2000; - break; + case Species.GALAR_SLOWPOKE: + break; + case Species.ETERNAL_FLOETTE: + break; + case Species.BLOODMOON_URSALUNA: + break; + default: + speciesId = speciesId % 2000; + break; } } let ret = speciesId.toString(); @@ -403,43 +403,44 @@ export abstract class PokemonSpeciesForm { } const formKey = forms[formIndex || 0].formKey; switch (formKey) { - case SpeciesFormKey.MEGA: - case SpeciesFormKey.MEGA_X: - case SpeciesFormKey.MEGA_Y: - case SpeciesFormKey.GIGANTAMAX: - case SpeciesFormKey.GIGANTAMAX_SINGLE: - case SpeciesFormKey.GIGANTAMAX_RAPID: - case "white": - case "black": - case "therian": - case "sky": - case "gorging": - case "gulping": - case "no-ice": - case "hangry": - case "crowned": - case "eternamax": - case "four": - case "droopy": - case "stretchy": - case "hero": - case "roaming": - case "complete": - case "10": - case "10-pc": - case "super": - case "unbound": - case "pau": - case "pompom": - case "sensu": - case "dusk": - case "midnight": - case "school": - case "dawn-wings": - case "dusk-mane": - case "ultra": - ret += `-${formKey}`; - break; + case SpeciesFormKey.MEGA: + case SpeciesFormKey.MEGA_X: + case SpeciesFormKey.MEGA_Y: + case SpeciesFormKey.GIGANTAMAX: + case SpeciesFormKey.GIGANTAMAX_SINGLE: + case SpeciesFormKey.GIGANTAMAX_RAPID: + case "white": + case "black": + case "therian": + case "sky": + case "gorging": + case "gulping": + case "no-ice": + case "hangry": + case "crowned": + case "eternamax": + case "four": + case "droopy": + case "stretchy": + case "hero": + case "roaming": + case "complete": + case "10-complete": + case "10": + case "10-pc": + case "super": + case "unbound": + case "pau": + case "pompom": + case "sensu": + case "dusk": + case "midnight": + case "school": + case "dawn-wings": + case "dusk-mane": + case "ultra": + ret += `-${formKey}`; + break; } } return ret; @@ -635,19 +636,19 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali const form = this.forms[formIndex]; let key: string | null; switch (form.formKey) { - case SpeciesFormKey.MEGA: - case SpeciesFormKey.PRIMAL: - case SpeciesFormKey.ETERNAMAX: - case SpeciesFormKey.MEGA_X: - case SpeciesFormKey.MEGA_Y: - key = form.formKey; - break; - default: - if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) { - key = "gigantamax"; - } else { - key = null; - } + case SpeciesFormKey.MEGA: + case SpeciesFormKey.PRIMAL: + case SpeciesFormKey.ETERNAMAX: + case SpeciesFormKey.MEGA_X: + case SpeciesFormKey.MEGA_Y: + key = form.formKey; + break; + default: + if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) { + key = "gigantamax"; + } else { + key = null; + } } if (key) { @@ -689,18 +690,18 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali */ private getStrengthLevelDiff(strength: PartyMemberStrength): integer { switch (Math.min(strength, PartyMemberStrength.STRONGER)) { - case PartyMemberStrength.WEAKEST: - return 60; - case PartyMemberStrength.WEAKER: - return 40; - case PartyMemberStrength.WEAK: - return 20; - case PartyMemberStrength.AVERAGE: - return 8; - case PartyMemberStrength.STRONG: - return 4; - default: - return 0; + case PartyMemberStrength.WEAKEST: + return 60; + case PartyMemberStrength.WEAKER: + return 40; + case PartyMemberStrength.WEAK: + return 20; + case PartyMemberStrength.AVERAGE: + return 8; + case PartyMemberStrength.STRONG: + return 4; + default: + return 0; } } @@ -975,7 +976,7 @@ export function initSpecies() { new PokemonSpecies(Species.VENUSAUR, 1, false, false, false, "Seed Pokémon", Type.GRASS, Type.POISON, 2, 100, Abilities.OVERGROW, Abilities.NONE, Abilities.CHLOROPHYLL, 525, 80, 82, 83, 100, 100, 80, 45, 50, 263, GrowthRate.MEDIUM_SLOW, 87.5, true, true, new PokemonForm("Normal", "", Type.GRASS, Type.POISON, 2, 100, Abilities.OVERGROW, Abilities.NONE, Abilities.CHLOROPHYLL, 525, 80, 82, 83, 100, 100, 80, 45, 50, 263, true, null, true), new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.GRASS, Type.POISON, 2.4, 155.5, Abilities.THICK_FAT, Abilities.THICK_FAT, Abilities.THICK_FAT, 625, 80, 100, 123, 122, 120, 80, 45, 50, 263, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, Type.POISON, 24, 999.9, Abilities.EFFECT_SPORE, Abilities.EFFECT_SPORE, Abilities.EFFECT_SPORE, 625, 120, 82, 98, 130, 115, 80, 45, 50, 263, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, Type.POISON, 24, 999.9, Abilities.EFFECT_SPORE, Abilities.NONE, Abilities.EFFECT_SPORE, 625, 120, 82, 98, 130, 115, 80, 45, 50, 263, true), ), new PokemonSpecies(Species.CHARMANDER, 1, false, false, false, "Lizard Pokémon", Type.FIRE, null, 0.6, 8.5, Abilities.BLAZE, Abilities.NONE, Abilities.SOLAR_POWER, 309, 39, 52, 43, 60, 50, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.CHARMELEON, 1, false, false, false, "Flame Pokémon", Type.FIRE, null, 1.1, 19, Abilities.BLAZE, Abilities.NONE, Abilities.SOLAR_POWER, 405, 58, 64, 58, 80, 65, 80, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), @@ -983,20 +984,20 @@ export function initSpecies() { new PokemonForm("Normal", "", Type.FIRE, Type.FLYING, 1.7, 90.5, Abilities.BLAZE, Abilities.NONE, Abilities.SOLAR_POWER, 534, 78, 84, 78, 109, 85, 100, 45, 50, 267, false, null, true), new PokemonForm("Mega X", SpeciesFormKey.MEGA_X, Type.FIRE, Type.DRAGON, 1.7, 110.5, Abilities.TOUGH_CLAWS, Abilities.NONE, Abilities.TOUGH_CLAWS, 634, 78, 130, 111, 130, 85, 100, 45, 50, 267), new PokemonForm("Mega Y", SpeciesFormKey.MEGA_Y, Type.FIRE, Type.FLYING, 1.7, 100.5, Abilities.DROUGHT, Abilities.NONE, Abilities.DROUGHT, 634, 78, 104, 78, 159, 115, 100, 45, 50, 267), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, Type.FLYING, 28, 999.9, Abilities.BERSERK, Abilities.BERSERK, Abilities.BERSERK, 634, 118, 84, 93, 139, 110, 100, 45, 50, 267), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, Type.FLYING, 28, 999.9, Abilities.BERSERK, Abilities.NONE, Abilities.BERSERK, 634, 118, 84, 93, 139, 100, 100, 45, 50, 267), ), new PokemonSpecies(Species.SQUIRTLE, 1, false, false, false, "Tiny Turtle Pokémon", Type.WATER, null, 0.5, 9, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 314, 44, 48, 65, 50, 64, 43, 45, 50, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.WARTORTLE, 1, false, false, false, "Turtle Pokémon", Type.WATER, null, 1, 22.5, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 405, 59, 63, 80, 65, 80, 58, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.BLASTOISE, 1, false, false, false, "Shellfish Pokémon", Type.WATER, null, 1.6, 85.5, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 530, 79, 83, 100, 85, 105, 78, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, new PokemonForm("Normal", "", Type.WATER, null, 1.6, 85.5, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 530, 79, 83, 100, 85, 105, 78, 45, 50, 265, false, null, true), new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.WATER, null, 1.6, 101.1, Abilities.MEGA_LAUNCHER, Abilities.NONE, Abilities.MEGA_LAUNCHER, 630, 79, 103, 120, 135, 115, 78, 45, 50, 265), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, Type.STEEL, 25, 999.9, Abilities.SHELL_ARMOR, Abilities.SHELL_ARMOR, Abilities.SHELL_ARMOR, 630, 119, 83, 135, 115, 110, 68, 45, 50, 265), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, Type.STEEL, 25, 999.9, Abilities.SHELL_ARMOR, Abilities.NONE, Abilities.SHELL_ARMOR, 630, 119, 83, 135, 115, 110, 68, 45, 50, 265), ), new PokemonSpecies(Species.CATERPIE, 1, false, false, false, "Worm Pokémon", Type.BUG, null, 0.3, 2.9, Abilities.SHIELD_DUST, Abilities.NONE, Abilities.RUN_AWAY, 195, 45, 30, 35, 20, 20, 45, 255, 50, 39, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.METAPOD, 1, false, false, false, "Cocoon Pokémon", Type.BUG, null, 0.7, 9.9, Abilities.SHED_SKIN, Abilities.NONE, Abilities.SHED_SKIN, 205, 50, 20, 55, 25, 25, 30, 120, 50, 72, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.BUTTERFREE, 1, false, false, false, "Butterfly Pokémon", Type.BUG, Type.FLYING, 1.1, 32, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, GrowthRate.MEDIUM_FAST, 50, true, true, new PokemonForm("Normal", "", Type.BUG, Type.FLYING, 1.1, 32, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, true, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.BUG, Type.FLYING, 17, 999.9, Abilities.COMPOUND_EYES, Abilities.COMPOUND_EYES, Abilities.COMPOUND_EYES, 495, 85, 35, 80, 120, 90, 85, 45, 50, 198, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.BUG, Type.FLYING, 17, 999.9, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.COMPOUND_EYES, 495, 85, 35, 80, 120, 90, 85, 45, 50, 198, true), ), new PokemonSpecies(Species.WEEDLE, 1, false, false, false, "Hairy Bug Pokémon", Type.BUG, Type.POISON, 0.3, 3.2, Abilities.SHIELD_DUST, Abilities.NONE, Abilities.RUN_AWAY, 195, 40, 35, 30, 20, 20, 50, 255, 70, 39, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.KAKUNA, 1, false, false, false, "Cocoon Pokémon", Type.BUG, Type.POISON, 0.6, 10, Abilities.SHED_SKIN, Abilities.NONE, Abilities.SHED_SKIN, 205, 45, 25, 50, 25, 25, 35, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false), @@ -1025,7 +1026,7 @@ export function initSpecies() { new PokemonForm("Cute Cosplay", "cute-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom new PokemonForm("Smart Cosplay", "smart-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom new PokemonForm("Tough Cosplay", "tough-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.ELECTRIC, null, 21, 999.9, Abilities.LIGHTNING_ROD, Abilities.LIGHTNING_ROD, Abilities.LIGHTNING_ROD, 530, 125, 95, 60, 90, 70, 90, 190, 50, 112), //+100 BST from Partner Form + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.ELECTRIC, null, 21, 999.9, Abilities.LIGHTNING_ROD, Abilities.NONE, Abilities.LIGHTNING_ROD, 530, 125, 95, 60, 90, 70, 90, 190, 50, 112), //+100 BST from Partner Form ), new PokemonSpecies(Species.RAICHU, 1, false, false, false, "Mouse Pokémon", Type.ELECTRIC, null, 0.8, 30, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 485, 60, 90, 55, 90, 80, 110, 75, 50, 243, GrowthRate.MEDIUM_FAST, 50, true), new PokemonSpecies(Species.SANDSHREW, 1, false, false, false, "Mouse Pokémon", Type.GROUND, null, 0.6, 12, Abilities.SAND_VEIL, Abilities.NONE, Abilities.SAND_RUSH, 300, 50, 75, 85, 20, 30, 40, 255, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), @@ -1110,7 +1111,7 @@ export function initSpecies() { new PokemonSpecies(Species.GENGAR, 1, false, false, false, "Shadow Pokémon", Type.GHOST, Type.POISON, 1.5, 40.5, Abilities.CURSED_BODY, Abilities.NONE, Abilities.NONE, 500, 60, 65, 60, 130, 75, 110, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, false, true, new PokemonForm("Normal", "", Type.GHOST, Type.POISON, 1.5, 40.5, Abilities.CURSED_BODY, Abilities.NONE, Abilities.NONE, 500, 60, 65, 60, 130, 75, 110, 45, 50, 250, false, null, true), new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.GHOST, Type.POISON, 1.4, 40.5, Abilities.SHADOW_TAG, Abilities.NONE, Abilities.NONE, 600, 60, 65, 80, 170, 95, 130, 45, 50, 250), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GHOST, Type.POISON, 20, 999.9, Abilities.CURSED_BODY, Abilities.CURSED_BODY, Abilities.CURSED_BODY, 600, 140, 65, 70, 140, 85, 100, 45, 50, 250), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GHOST, Type.POISON, 20, 999.9, Abilities.CURSED_BODY, Abilities.NONE, Abilities.NONE, 600, 140, 65, 70, 140, 85, 100, 45, 50, 250), ), new PokemonSpecies(Species.ONIX, 1, false, false, false, "Rock Snake Pokémon", Type.ROCK, Type.GROUND, 8.8, 210, Abilities.ROCK_HEAD, Abilities.STURDY, Abilities.WEAK_ARMOR, 385, 35, 45, 160, 30, 45, 70, 45, 50, 77, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.DROWZEE, 1, false, false, false, "Hypnosis Pokémon", Type.PSYCHIC, null, 1, 32.4, Abilities.INSOMNIA, Abilities.FOREWARN, Abilities.INNER_FOCUS, 328, 60, 48, 45, 43, 90, 42, 190, 70, 66, GrowthRate.MEDIUM_FAST, 50, false), @@ -1118,7 +1119,7 @@ export function initSpecies() { new PokemonSpecies(Species.KRABBY, 1, false, false, false, "River Crab Pokémon", Type.WATER, null, 0.4, 6.5, Abilities.HYPER_CUTTER, Abilities.SHELL_ARMOR, Abilities.SHEER_FORCE, 325, 30, 105, 90, 25, 25, 50, 225, 50, 65, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.KINGLER, 1, false, false, false, "Pincer Pokémon", Type.WATER, null, 1.3, 60, Abilities.HYPER_CUTTER, Abilities.SHELL_ARMOR, Abilities.SHEER_FORCE, 475, 55, 130, 115, 50, 50, 75, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, true, new PokemonForm("Normal", "", Type.WATER, null, 1.3, 60, Abilities.HYPER_CUTTER, Abilities.SHELL_ARMOR, Abilities.SHEER_FORCE, 475, 55, 130, 115, 50, 50, 75, 60, 50, 166, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 19, 999.9, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, 575, 90, 155, 140, 50, 80, 70, 60, 50, 166), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 19, 999.9, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, 575, 90, 155, 140, 50, 70, 70, 60, 50, 166), ), new PokemonSpecies(Species.VOLTORB, 1, false, false, false, "Ball Pokémon", Type.ELECTRIC, null, 0.5, 10.4, Abilities.SOUNDPROOF, Abilities.STATIC, Abilities.AFTERMATH, 330, 40, 30, 50, 55, 55, 100, 190, 70, 66, GrowthRate.MEDIUM_FAST, null, false), new PokemonSpecies(Species.ELECTRODE, 1, false, false, false, "Ball Pokémon", Type.ELECTRIC, null, 1.2, 66.6, Abilities.SOUNDPROOF, Abilities.STATIC, Abilities.AFTERMATH, 490, 60, 50, 70, 80, 80, 150, 60, 70, 172, GrowthRate.MEDIUM_FAST, null, false), @@ -2135,7 +2136,8 @@ export function initSpecies() { new PokemonForm("10% Forme", "10", Type.DRAGON, Type.GROUND, 1.2, 33.5, Abilities.AURA_BREAK, Abilities.NONE, Abilities.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 300, false, null, true), new PokemonForm("50% Forme Power Construct", "50-pc", Type.DRAGON, Type.GROUND, 5, 305, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, false, "", true), new PokemonForm("10% Forme Power Construct", "10-pc", Type.DRAGON, Type.GROUND, 1.2, 33.5, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 300, false, "10", true), - new PokemonForm("Complete Forme", "complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300), + new PokemonForm("Complete Forme (50% PC)", "complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300), + new PokemonForm("Complete Forme (10% PC)", "10-complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300, false, "complete"), ), new PokemonSpecies(Species.DIANCIE, 6, false, false, true, "Jewel Pokémon", Type.ROCK, Type.FAIRY, 0.7, 8.8, Abilities.CLEAR_BODY, Abilities.NONE, Abilities.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, GrowthRate.SLOW, null, false, true, new PokemonForm("Normal", "", Type.ROCK, Type.FAIRY, 0.7, 8.8, Abilities.CLEAR_BODY, Abilities.NONE, Abilities.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, false, null, true), @@ -2298,25 +2300,25 @@ export function initSpecies() { new PokemonSpecies(Species.MELTAN, 7, false, false, true, "Hex Nut Pokémon", Type.STEEL, null, 0.2, 8, Abilities.MAGNET_PULL, Abilities.NONE, Abilities.NONE, 300, 46, 65, 65, 55, 35, 34, 3, 0, 150, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.MELMETAL, 7, false, false, true, "Hex Nut Pokémon", Type.STEEL, null, 2.5, 800, Abilities.IRON_FIST, Abilities.NONE, Abilities.NONE, 600, 135, 143, 143, 80, 65, 34, 3, 0, 300, GrowthRate.SLOW, null, false, true, new PokemonForm("Normal", "", Type.STEEL, null, 2.5, 800, Abilities.IRON_FIST, Abilities.NONE, Abilities.NONE, 600, 135, 143, 143, 80, 65, 34, 3, 0, 300, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, null, 25, 999.9, Abilities.IRON_FIST, Abilities.IRON_FIST, Abilities.IRON_FIST, 700, 175, 165, 155, 85, 75, 45, 3, 0, 300), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, null, 25, 999.9, Abilities.IRON_FIST, Abilities.NONE, Abilities.NONE, 700, 175, 165, 155, 85, 75, 45, 3, 0, 300), ), new PokemonSpecies(Species.GROOKEY, 8, false, false, false, "Chimp Pokémon", Type.GRASS, null, 0.3, 5, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 310, 50, 65, 50, 40, 40, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.THWACKEY, 8, false, false, false, "Beat Pokémon", Type.GRASS, null, 0.7, 14, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 420, 70, 85, 70, 55, 60, 80, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.RILLABOOM, 8, false, false, false, "Drummer Pokémon", Type.GRASS, null, 2.1, 90, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 530, 100, 125, 90, 60, 70, 85, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, new PokemonForm("Normal", "", Type.GRASS, null, 2.1, 90, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 530, 100, 125, 90, 60, 70, 85, 45, 50, 265, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, null, 28, 999.9, Abilities.GRASSY_SURGE, Abilities.GRASSY_SURGE, Abilities.GRASSY_SURGE, 630, 125, 150, 105, 85, 85, 80, 45, 50, 265), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, null, 28, 999.9, Abilities.GRASSY_SURGE, Abilities.NONE, Abilities.GRASSY_SURGE, 630, 125, 150, 105, 85, 85, 80, 45, 50, 265), ), new PokemonSpecies(Species.SCORBUNNY, 8, false, false, false, "Rabbit Pokémon", Type.FIRE, null, 0.3, 4.5, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 310, 50, 71, 40, 40, 40, 69, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.RABOOT, 8, false, false, false, "Rabbit Pokémon", Type.FIRE, null, 0.6, 9, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 420, 65, 86, 60, 55, 60, 94, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.CINDERACE, 8, false, false, false, "Striker Pokémon", Type.FIRE, null, 1.4, 33, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 530, 80, 116, 75, 65, 75, 119, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, new PokemonForm("Normal", "", Type.FIRE, null, 1.4, 33, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 530, 80, 116, 75, 65, 75, 119, 45, 50, 265, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, null, 27, 999.9, Abilities.LIBERO, Abilities.LIBERO, Abilities.LIBERO, 630, 100, 146, 80, 90, 80, 134, 45, 50, 265), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, null, 27, 999.9, Abilities.LIBERO, Abilities.NONE, Abilities.LIBERO, 630, 100, 146, 80, 90, 80, 134, 45, 50, 265), ), new PokemonSpecies(Species.SOBBLE, 8, false, false, false, "Water Lizard Pokémon", Type.WATER, null, 0.3, 4, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 310, 50, 40, 40, 70, 40, 70, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.DRIZZILE, 8, false, false, false, "Water Lizard Pokémon", Type.WATER, null, 0.7, 11.5, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 420, 65, 60, 55, 95, 55, 90, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.INTELEON, 8, false, false, false, "Secret Agent Pokémon", Type.WATER, null, 1.9, 45.2, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 530, 70, 85, 65, 125, 65, 120, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, new PokemonForm("Normal", "", Type.WATER, null, 1.9, 45.2, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 530, 70, 85, 65, 125, 65, 120, 45, 50, 265, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 40, 999.9, Abilities.SNIPER, Abilities.SNIPER, Abilities.SNIPER, 630, 95, 97, 77, 147, 77, 137, 45, 50, 265), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 40, 999.9, Abilities.SNIPER, Abilities.NONE, Abilities.SNIPER, 630, 95, 97, 77, 147, 77, 137, 45, 50, 265), ), new PokemonSpecies(Species.SKWOVET, 8, false, false, false, "Cheeky Pokémon", Type.NORMAL, null, 0.3, 2.5, Abilities.CHEEK_POUCH, Abilities.NONE, Abilities.GLUTTONY, 275, 70, 55, 55, 35, 35, 25, 255, 50, 55, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.GREEDENT, 8, false, false, false, "Greedy Pokémon", Type.NORMAL, null, 0.6, 6, Abilities.CHEEK_POUCH, Abilities.NONE, Abilities.GLUTTONY, 460, 120, 95, 95, 55, 75, 20, 90, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), @@ -2422,7 +2424,7 @@ export function initSpecies() { new PokemonForm("Ruby Swirl", "ruby-swirl", Type.FAIRY, null, 0.3, 0.5, Abilities.SWEET_VEIL, Abilities.NONE, Abilities.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), new PokemonForm("Caramel Swirl", "caramel-swirl", Type.FAIRY, null, 0.3, 0.5, Abilities.SWEET_VEIL, Abilities.NONE, Abilities.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), new PokemonForm("Rainbow Swirl", "rainbow-swirl", Type.FAIRY, null, 0.3, 0.5, Abilities.SWEET_VEIL, Abilities.NONE, Abilities.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FAIRY, null, 30, 999.9, Abilities.MISTY_SURGE, Abilities.MISTY_SURGE, Abilities.MISTY_SURGE, 595, 135, 60, 75, 130, 131, 64, 100, 50, 173), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FAIRY, null, 30, 999.9, Abilities.MISTY_SURGE, Abilities.NONE, Abilities.MISTY_SURGE, 595, 135, 60, 75, 130, 131, 64, 100, 50, 173), ), new PokemonSpecies(Species.FALINKS, 8, false, false, false, "Formation Pokémon", Type.FIGHTING, null, 3, 62, Abilities.BATTLE_ARMOR, Abilities.NONE, Abilities.DEFIANT, 470, 65, 100, 100, 70, 60, 75, 45, 50, 165, GrowthRate.MEDIUM_FAST, null, false), new PokemonSpecies(Species.PINCURCHIN, 8, false, false, false, "Sea Urchin Pokémon", Type.ELECTRIC, null, 0.3, 1, Abilities.LIGHTNING_ROD, Abilities.NONE, Abilities.ELECTRIC_SURGE, 435, 48, 101, 95, 91, 85, 15, 75, 50, 152, GrowthRate.MEDIUM_FAST, 50, false), @@ -2444,7 +2446,7 @@ export function initSpecies() { new PokemonSpecies(Species.CUFANT, 8, false, false, false, "Copperderm Pokémon", Type.STEEL, null, 1.2, 100, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.HEAVY_METAL, 330, 72, 80, 49, 40, 49, 40, 190, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.COPPERAJAH, 8, false, false, false, "Copperderm Pokémon", Type.STEEL, null, 3, 650, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.HEAVY_METAL, 500, 122, 130, 69, 80, 69, 30, 90, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, true, new PokemonForm("Normal", "", Type.STEEL, null, 3, 650, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.HEAVY_METAL, 500, 122, 130, 69, 80, 69, 30, 90, 50, 175, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, Type.GROUND, 23, 999.9, Abilities.MOLD_BREAKER, Abilities.MOLD_BREAKER, Abilities.MOLD_BREAKER, 600, 167, 155, 89, 80, 89, 20, 90, 50, 175), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, Type.GROUND, 23, 999.9, Abilities.MOLD_BREAKER, Abilities.NONE, Abilities.MOLD_BREAKER, 600, 167, 155, 89, 80, 89, 20, 90, 50, 175), ), new PokemonSpecies(Species.DRACOZOLT, 8, false, false, false, "Fossil Pokémon", Type.ELECTRIC, Type.DRAGON, 1.8, 190, Abilities.VOLT_ABSORB, Abilities.HUSTLE, Abilities.SAND_RUSH, 505, 90, 100, 90, 80, 70, 75, 45, 50, 177, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.ARCTOZOLT, 8, false, false, false, "Fossil Pokémon", Type.ELECTRIC, Type.ICE, 2.3, 150, Abilities.VOLT_ABSORB, Abilities.STATIC, Abilities.SLUSH_RUSH, 505, 90, 100, 90, 90, 80, 55, 45, 50, 177, GrowthRate.SLOW, null, false), diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index ffe32a02aeb..4319985f43a 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -26,20 +26,20 @@ export class Status { function getStatusEffectMessageKey(statusEffect: StatusEffect | undefined): string { switch (statusEffect) { - case StatusEffect.POISON: - return "statusEffect:poison"; - case StatusEffect.TOXIC: - return "statusEffect:toxic"; - case StatusEffect.PARALYSIS: - return "statusEffect:paralysis"; - case StatusEffect.SLEEP: - return "statusEffect:sleep"; - case StatusEffect.FREEZE: - return "statusEffect:freeze"; - case StatusEffect.BURN: - return "statusEffect:burn"; - default: - return "statusEffect:none"; + case StatusEffect.POISON: + return "statusEffect:poison"; + case StatusEffect.TOXIC: + return "statusEffect:toxic"; + case StatusEffect.PARALYSIS: + return "statusEffect:paralysis"; + case StatusEffect.SLEEP: + return "statusEffect:sleep"; + case StatusEffect.FREEZE: + return "statusEffect:freeze"; + case StatusEffect.BURN: + return "statusEffect:burn"; + default: + return "statusEffect:none"; } } @@ -90,14 +90,14 @@ export function getStatusEffectDescriptor(statusEffect: StatusEffect): string { export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect): number { switch (statusEffect) { - case StatusEffect.POISON: - case StatusEffect.TOXIC: - case StatusEffect.PARALYSIS: - case StatusEffect.BURN: - return 1.5; - case StatusEffect.SLEEP: - case StatusEffect.FREEZE: - return 2.5; + case StatusEffect.POISON: + case StatusEffect.TOXIC: + case StatusEffect.PARALYSIS: + case StatusEffect.BURN: + return 1.5; + case StatusEffect.SLEEP: + case StatusEffect.FREEZE: + return 2.5; } return 1; diff --git a/src/data/terrain.ts b/src/data/terrain.ts index 6db68b92239..d8ee8d67925 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -34,21 +34,21 @@ export class Terrain { getAttackTypeMultiplier(attackType: Type): number { switch (this.terrainType) { - case TerrainType.ELECTRIC: - if (attackType === Type.ELECTRIC) { - return 1.3; - } - break; - case TerrainType.GRASSY: - if (attackType === Type.GRASS) { - return 1.3; - } - break; - case TerrainType.PSYCHIC: - if (attackType === Type.PSYCHIC) { - return 1.3; - } - break; + case TerrainType.ELECTRIC: + if (attackType === Type.ELECTRIC) { + return 1.3; + } + break; + case TerrainType.GRASSY: + if (attackType === Type.GRASS) { + return 1.3; + } + break; + case TerrainType.PSYCHIC: + if (attackType === Type.PSYCHIC) { + return 1.3; + } + break; } return 1; @@ -56,13 +56,13 @@ export class Terrain { isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean { switch (this.terrainType) { - case TerrainType.PSYCHIC: - if (!move.hasAttr(ProtectAttr)) { - const priority = new Utils.IntegerHolder(move.priority); - applyAbAttrs(ChangeMovePriorityAbAttr, user, null, false, move, priority); - // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain - return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()); - } + case TerrainType.PSYCHIC: + if (!move.hasAttr(ProtectAttr)) { + const priority = new Utils.IntegerHolder(move.priority); + applyAbAttrs(ChangeMovePriorityAbAttr, user, null, false, move, priority); + // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain + return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()); + } } return false; @@ -71,14 +71,14 @@ export class Terrain { export function getTerrainName(terrainType: TerrainType): string { switch (terrainType) { - case TerrainType.MISTY: - return i18next.t("terrain:misty"); - case TerrainType.ELECTRIC: - return i18next.t("terrain:electric"); - case TerrainType.GRASSY: - return i18next.t("terrain:grassy"); - case TerrainType.PSYCHIC: - return i18next.t("terrain:psychic"); + case TerrainType.MISTY: + return i18next.t("terrain:misty"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electric"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassy"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychic"); } return ""; @@ -87,14 +87,14 @@ export function getTerrainName(terrainType: TerrainType): string { export function getTerrainColor(terrainType: TerrainType): [ integer, integer, integer ] { switch (terrainType) { - case TerrainType.MISTY: - return [ 232, 136, 200 ]; - case TerrainType.ELECTRIC: - return [ 248, 248, 120 ]; - case TerrainType.GRASSY: - return [ 120, 200, 80 ]; - case TerrainType.PSYCHIC: - return [ 160, 64, 160 ]; + case TerrainType.MISTY: + return [ 232, 136, 200 ]; + case TerrainType.ELECTRIC: + return [ 248, 248, 120 ]; + case TerrainType.GRASSY: + return [ 120, 200, 80 ]; + case TerrainType.PSYCHIC: + return [ 160, 64, 160 ]; } return [ 0, 0, 0 ]; diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index bc6596c74bd..fcc13975270 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -299,65 +299,65 @@ export class TrainerConfig { getDerivedType(trainerTypeToDeriveFrom: TrainerType | null = null): TrainerType { let trainerType = trainerTypeToDeriveFrom ? trainerTypeToDeriveFrom : this.trainerType; switch (trainerType) { - case TrainerType.RIVAL_2: - case TrainerType.RIVAL_3: - case TrainerType.RIVAL_4: - case TrainerType.RIVAL_5: - case TrainerType.RIVAL_6: - trainerType = TrainerType.RIVAL; - break; - case TrainerType.LANCE_CHAMPION: - trainerType = TrainerType.LANCE; - break; - case TrainerType.LARRY_ELITE: - trainerType = TrainerType.LARRY; - break; - case TrainerType.ROCKET_BOSS_GIOVANNI_1: - case TrainerType.ROCKET_BOSS_GIOVANNI_2: - trainerType = TrainerType.GIOVANNI; - break; - case TrainerType.MAXIE_2: - trainerType = TrainerType.MAXIE; - break; - case TrainerType.ARCHIE_2: - trainerType = TrainerType.ARCHIE; - break; - case TrainerType.CYRUS_2: - trainerType = TrainerType.CYRUS; - break; - case TrainerType.GHETSIS_2: - trainerType = TrainerType.GHETSIS; - break; - case TrainerType.LYSANDRE_2: - trainerType = TrainerType.LYSANDRE; - break; - case TrainerType.LUSAMINE_2: - trainerType = TrainerType.LUSAMINE; - break; - case TrainerType.GUZMA_2: - trainerType = TrainerType.GUZMA; - break; - case TrainerType.ROSE_2: - trainerType = TrainerType.ROSE; - break; - case TrainerType.PENNY_2: - trainerType = TrainerType.PENNY; - break; - case TrainerType.MARNIE_ELITE: - trainerType = TrainerType.MARNIE; - break; - case TrainerType.NESSA_ELITE: - trainerType = TrainerType.NESSA; - break; - case TrainerType.BEA_ELITE: - trainerType = TrainerType.BEA; - break; - case TrainerType.ALLISTER_ELITE: - trainerType = TrainerType.ALLISTER; - break; - case TrainerType.RAIHAN_ELITE: - trainerType = TrainerType.RAIHAN; - break; + case TrainerType.RIVAL_2: + case TrainerType.RIVAL_3: + case TrainerType.RIVAL_4: + case TrainerType.RIVAL_5: + case TrainerType.RIVAL_6: + trainerType = TrainerType.RIVAL; + break; + case TrainerType.LANCE_CHAMPION: + trainerType = TrainerType.LANCE; + break; + case TrainerType.LARRY_ELITE: + trainerType = TrainerType.LARRY; + break; + case TrainerType.ROCKET_BOSS_GIOVANNI_1: + case TrainerType.ROCKET_BOSS_GIOVANNI_2: + trainerType = TrainerType.GIOVANNI; + break; + case TrainerType.MAXIE_2: + trainerType = TrainerType.MAXIE; + break; + case TrainerType.ARCHIE_2: + trainerType = TrainerType.ARCHIE; + break; + case TrainerType.CYRUS_2: + trainerType = TrainerType.CYRUS; + break; + case TrainerType.GHETSIS_2: + trainerType = TrainerType.GHETSIS; + break; + case TrainerType.LYSANDRE_2: + trainerType = TrainerType.LYSANDRE; + break; + case TrainerType.LUSAMINE_2: + trainerType = TrainerType.LUSAMINE; + break; + case TrainerType.GUZMA_2: + trainerType = TrainerType.GUZMA; + break; + case TrainerType.ROSE_2: + trainerType = TrainerType.ROSE; + break; + case TrainerType.PENNY_2: + trainerType = TrainerType.PENNY; + break; + case TrainerType.MARNIE_ELITE: + trainerType = TrainerType.MARNIE; + break; + case TrainerType.NESSA_ELITE: + trainerType = TrainerType.NESSA; + break; + case TrainerType.BEA_ELITE: + trainerType = TrainerType.BEA; + break; + case TrainerType.ALLISTER_ELITE: + trainerType = TrainerType.ALLISTER; + break; + case TrainerType.RAIHAN_ELITE: + trainerType = TrainerType.RAIHAN; + break; } return trainerType; @@ -564,104 +564,104 @@ export class TrainerConfig { speciesPoolPerEvilTeamAdmin(team): TrainerTierPools { team = team.toLowerCase(); switch (team) { - case "rocket": { - return { - [TrainerPoolTier.COMMON]: [ Species.RATTATA, Species.KOFFING, Species.EKANS, Species.ZUBAT, Species.MAGIKARP, Species.HOUNDOUR, Species.ONIX, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB, Species.DROWZEE, Species.VILEPLUME ], - [TrainerPoolTier.UNCOMMON]: [ Species.PORYGON, Species.MANKEY, Species.MAGNEMITE, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE, Species.PALDEA_TAUROS, Species.OMANYTE, Species.KABUTO, Species.MAGBY, Species.ELEKID ], - [TrainerPoolTier.RARE]: [ Species.DRATINI, Species.LARVITAR ] - }; - } - case "magma": { - return { - [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.SLUGMA, Species.SOLROCK, Species.HIPPOPOTAS, Species.BALTOY, Species.ROLYCOLY, Species.GLIGAR, Species.TORKOAL, Species.HOUNDOUR, Species.MAGBY ], - [TrainerPoolTier.UNCOMMON]: [ Species.TRAPINCH, Species.SILICOBRA, Species.RHYHORN, Species.ANORITH, Species.LILEEP, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.BARBOACH ], - [TrainerPoolTier.RARE]: [ Species.CAPSAKID, Species.CHARCADET ] - }; - } - case "aqua": { - return { - [TrainerPoolTier.COMMON]: [ Species.CORPHISH, Species.SPHEAL, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.LOTAD, Species.WAILMER, Species.REMORAID ], - [TrainerPoolTier.UNCOMMON]: [ Species.MANTYKE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.DHELMISE, Species.CLOBBOPUS, Species.FEEBAS, Species.PALDEA_WOOPER, Species.HORSEA, Species.SKRELP ], - [TrainerPoolTier.RARE]: [ Species.DONDOZO, Species.BASCULEGION ] - }; - } - case "galactic": { - return { - [TrainerPoolTier.COMMON]: [ Species.BRONZOR, Species.SWINUB, Species.YANMA, Species.LICKITUNG, Species.TANGELA, Species.MAGBY, Species.ELEKID, Species.SKORUPI, Species.ZUBAT, Species.MURKROW, Species.MAGIKARP, Species.VOLTORB ], - [TrainerPoolTier.UNCOMMON]: [ Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.SNEASEL, Species.DUSKULL, Species.ROTOM, Species.HISUI_VOLTORB, Species.GLIGAR, Species.ABRA ], - [TrainerPoolTier.RARE]: [ Species.URSALUNA, Species.HISUI_LILLIGANT, Species.SPIRITOMB, Species.HISUI_SNEASEL ] - }; - } - case "plasma": { - return { - [TrainerPoolTier.COMMON]: [ Species.YAMASK, Species.ROGGENROLA, Species.JOLTIK, Species.TYMPOLE, Species.FRILLISH, Species.FERROSEED, Species.SANDILE, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.CUBCHOO, Species.VANILLITE ], - [TrainerPoolTier.UNCOMMON]: [ Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK, Species.TYNAMO, Species.GALAR_DARUMAKA, Species.GOLETT, Species.MIENFOO, Species.DURANT, Species.SIGILYPH ], - [TrainerPoolTier.RARE]: [ Species.HISUI_ZORUA, Species.AXEW, Species.DEINO, Species.HISUI_BRAVIARY ] - }; - } - case "flare": { - return { - [TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY ], - [TrainerPoolTier.UNCOMMON]: [ Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO ], - [TrainerPoolTier.RARE]: [ Species.NOIVERN, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ] - }; - } - case "aether": { - return { - [TrainerPoolTier.COMMON]: [ Species.BRUXISH, Species.SLOWPOKE, Species.BALTOY, Species.EXEGGCUTE, Species.ABRA, Species.ALOLA_RAICHU, Species.ELGYEM, Species.NATU, Species.BLIPBUG, Species.GIRAFARIG, Species.ORANGURU ], - [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.MEDITITE, Species.BELDUM, Species.HATENNA, Species.INKAY, Species.RALTS, Species.GALAR_MR_MIME ], - [TrainerPoolTier.RARE]: [ Species.ARMAROUGE, Species.HISUI_BRAVIARY, Species.PORYGON ] - }; - } - case "skull": { - return { - [TrainerPoolTier.COMMON]: [ Species.MAREANIE, Species.ALOLA_GRIMER, Species.GASTLY, Species.ZUBAT, Species.FOMANTIS, Species.VENIPEDE, Species.BUDEW, Species.KOFFING, Species.STUNKY, Species.CROAGUNK, Species.NIDORAN_F ], - [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.SKORUPI, Species.PALDEA_WOOPER, Species.VULLABY, Species.HISUI_QWILFISH, Species.GLIMMET ], - [TrainerPoolTier.RARE]: [ Species.SKRELP, Species.HISUI_SNEASEL ] - }; - } - case "macro": { - return { - [TrainerPoolTier.COMMON]: [ Species.HATENNA, Species.FEEBAS, Species.BOUNSWEET, Species.SALANDIT, Species.GALAR_PONYTA, Species.GOTHITA, Species.FROSLASS, Species.VULPIX, Species.FRILLISH, Species.ODDISH, Species.SINISTEA ], - [TrainerPoolTier.UNCOMMON]: [ Species.VULLABY, Species.MAREANIE, Species.ALOLA_VULPIX, Species.TOGEPI, Species.GALAR_CORSOLA, Species.APPLIN ], - [TrainerPoolTier.RARE]: [ Species.TINKATINK, Species.HISUI_LILLIGANT ] - }; - } - case "star_1": { - return { - [TrainerPoolTier.COMMON]: [ Species.MURKROW, Species.SEEDOT, Species.CACNEA, Species.STUNKY, Species.SANDILE, Species.NYMBLE, Species.MASCHIFF, Species.GALAR_ZIGZAGOON ], - [TrainerPoolTier.UNCOMMON]: [ Species.UMBREON, Species.SNEASEL, Species.CORPHISH, Species.ZORUA, Species.INKAY, Species.BOMBIRDIER ], - [TrainerPoolTier.RARE]: [ Species.DEINO, Species.SPRIGATITO ] - }; - } - case "star_2": { - return { - [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.HOUNDOUR, Species.NUMEL, Species.LITWICK, Species.FLETCHLING, Species.LITLEO, Species.ROLYCOLY, Species.CAPSAKID ], - [TrainerPoolTier.UNCOMMON]: [ Species.PONYTA, Species.FLAREON, Species.MAGBY, Species.TORKOAL, Species.SALANDIT, Species.TURTONATOR ], - [TrainerPoolTier.RARE]: [ Species.LARVESTA, Species.FUECOCO ] - }; - } - case "star_3": { - return { - [TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ], - [TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ], - [TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ] - }; - } - case "star_4": { - return { - [TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ], - [TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ], - [TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ] - }; - } - case "star_5": { - return { - [TrainerPoolTier.COMMON]: [ Species.SHROOMISH, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.SCRAGGY, Species.MIENFOO, Species.PAWMI, Species.PALDEA_TAUROS ], - [TrainerPoolTier.UNCOMMON]: [ Species.RIOLU, Species.TIMBURR, Species.HAWLUCHA, Species.PASSIMIAN, Species.FALINKS, Species.FLAMIGO ], - [TrainerPoolTier.RARE]: [ Species.JANGMO_O, Species.QUAXLY ] - }; - } + case "rocket": { + return { + [TrainerPoolTier.COMMON]: [ Species.RATTATA, Species.KOFFING, Species.EKANS, Species.ZUBAT, Species.MAGIKARP, Species.HOUNDOUR, Species.ONIX, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB, Species.DROWZEE, Species.VILEPLUME ], + [TrainerPoolTier.UNCOMMON]: [ Species.PORYGON, Species.MANKEY, Species.MAGNEMITE, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE, Species.PALDEA_TAUROS, Species.OMANYTE, Species.KABUTO, Species.MAGBY, Species.ELEKID ], + [TrainerPoolTier.RARE]: [ Species.DRATINI, Species.LARVITAR ] + }; + } + case "magma": { + return { + [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.SLUGMA, Species.SOLROCK, Species.HIPPOPOTAS, Species.BALTOY, Species.ROLYCOLY, Species.GLIGAR, Species.TORKOAL, Species.HOUNDOUR, Species.MAGBY ], + [TrainerPoolTier.UNCOMMON]: [ Species.TRAPINCH, Species.SILICOBRA, Species.RHYHORN, Species.ANORITH, Species.LILEEP, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ], + [TrainerPoolTier.RARE]: [ Species.CAPSAKID, Species.CHARCADET ] + }; + } + case "aqua": { + return { + [TrainerPoolTier.COMMON]: [ Species.CORPHISH, Species.SPHEAL, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.LOTAD, Species.WAILMER, Species.REMORAID, Species.BARBOACH ], + [TrainerPoolTier.UNCOMMON]: [ Species.MANTYKE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.DHELMISE, Species.CLOBBOPUS, Species.FEEBAS, Species.PALDEA_WOOPER, Species.HORSEA, Species.SKRELP ], + [TrainerPoolTier.RARE]: [ Species.DONDOZO, Species.BASCULEGION ] + }; + } + case "galactic": { + return { + [TrainerPoolTier.COMMON]: [ Species.BRONZOR, Species.SWINUB, Species.YANMA, Species.LICKITUNG, Species.TANGELA, Species.MAGBY, Species.ELEKID, Species.SKORUPI, Species.ZUBAT, Species.MURKROW, Species.MAGIKARP, Species.VOLTORB ], + [TrainerPoolTier.UNCOMMON]: [ Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.SNEASEL, Species.DUSKULL, Species.ROTOM, Species.HISUI_VOLTORB, Species.GLIGAR, Species.ABRA ], + [TrainerPoolTier.RARE]: [ Species.URSALUNA, Species.HISUI_LILLIGANT, Species.SPIRITOMB, Species.HISUI_SNEASEL ] + }; + } + case "plasma": { + return { + [TrainerPoolTier.COMMON]: [ Species.YAMASK, Species.ROGGENROLA, Species.JOLTIK, Species.TYMPOLE, Species.FRILLISH, Species.FERROSEED, Species.SANDILE, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.CUBCHOO, Species.VANILLITE ], + [TrainerPoolTier.UNCOMMON]: [ Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK, Species.TYNAMO, Species.GALAR_DARUMAKA, Species.GOLETT, Species.MIENFOO, Species.DURANT, Species.SIGILYPH ], + [TrainerPoolTier.RARE]: [ Species.HISUI_ZORUA, Species.AXEW, Species.DEINO, Species.HISUI_BRAVIARY ] + }; + } + case "flare": { + return { + [TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.FOONGUS, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY ], + [TrainerPoolTier.UNCOMMON]: [ Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO ], + [TrainerPoolTier.RARE]: [ Species.NOIBAT, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ] + }; + } + case "aether": { + return { + [TrainerPoolTier.COMMON]: [ Species.BRUXISH, Species.SLOWPOKE, Species.BALTOY, Species.EXEGGCUTE, Species.ABRA, Species.ALOLA_RAICHU, Species.ELGYEM, Species.NATU, Species.BLIPBUG, Species.GIRAFARIG, Species.ORANGURU ], + [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.MEDITITE, Species.BELDUM, Species.HATENNA, Species.INKAY, Species.RALTS, Species.GALAR_MR_MIME ], + [TrainerPoolTier.RARE]: [ Species.ARMAROUGE, Species.HISUI_BRAVIARY, Species.PORYGON ] + }; + } + case "skull": { + return { + [TrainerPoolTier.COMMON]: [ Species.MAREANIE, Species.ALOLA_GRIMER, Species.GASTLY, Species.ZUBAT, Species.FOMANTIS, Species.VENIPEDE, Species.BUDEW, Species.KOFFING, Species.STUNKY, Species.CROAGUNK, Species.NIDORAN_F ], + [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.SKORUPI, Species.PALDEA_WOOPER, Species.VULLABY, Species.HISUI_QWILFISH, Species.GLIMMET ], + [TrainerPoolTier.RARE]: [ Species.SKRELP, Species.HISUI_SNEASEL ] + }; + } + case "macro": { + return { + [TrainerPoolTier.COMMON]: [ Species.HATENNA, Species.FEEBAS, Species.BOUNSWEET, Species.SALANDIT, Species.GALAR_PONYTA, Species.GOTHITA, Species.FROSLASS, Species.VULPIX, Species.FRILLISH, Species.ODDISH, Species.SINISTEA ], + [TrainerPoolTier.UNCOMMON]: [ Species.VULLABY, Species.MAREANIE, Species.ALOLA_VULPIX, Species.TOGEPI, Species.GALAR_CORSOLA, Species.APPLIN ], + [TrainerPoolTier.RARE]: [ Species.TINKATINK, Species.HISUI_LILLIGANT ] + }; + } + case "star_1": { + return { + [TrainerPoolTier.COMMON]: [ Species.MURKROW, Species.SEEDOT, Species.CACNEA, Species.STUNKY, Species.SANDILE, Species.NYMBLE, Species.MASCHIFF, Species.GALAR_ZIGZAGOON ], + [TrainerPoolTier.UNCOMMON]: [ Species.UMBREON, Species.SNEASEL, Species.CORPHISH, Species.ZORUA, Species.INKAY, Species.BOMBIRDIER ], + [TrainerPoolTier.RARE]: [ Species.DEINO, Species.SPRIGATITO ] + }; + } + case "star_2": { + return { + [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.HOUNDOUR, Species.NUMEL, Species.LITWICK, Species.FLETCHLING, Species.LITLEO, Species.ROLYCOLY, Species.CAPSAKID ], + [TrainerPoolTier.UNCOMMON]: [ Species.PONYTA, Species.FLAREON, Species.MAGBY, Species.TORKOAL, Species.SALANDIT, Species.TURTONATOR ], + [TrainerPoolTier.RARE]: [ Species.LARVESTA, Species.FUECOCO ] + }; + } + case "star_3": { + return { + [TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ], + [TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ], + [TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ] + }; + } + case "star_4": { + return { + [TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ], + [TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ], + [TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ] + }; + } + case "star_5": { + return { + [TrainerPoolTier.COMMON]: [ Species.SHROOMISH, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.SCRAGGY, Species.MIENFOO, Species.PAWMI, Species.PALDEA_TAUROS ], + [TrainerPoolTier.UNCOMMON]: [ Species.RIOLU, Species.TIMBURR, Species.HAWLUCHA, Species.PASSIMIAN, Species.FALINKS, Species.FLAMIGO ], + [TrainerPoolTier.RARE]: [ Species.JANGMO_O, Species.QUAXLY ] + }; + } } console.warn(`Evil team admin for ${team} not found. Returning empty species pools.`); @@ -1504,7 +1504,7 @@ export const trainerConfigs: TrainerConfigs = { .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.SLUGMA, Species.POOCHYENA, Species.NUMEL, Species.ZIGZAGOON, Species.DIGLETT, Species.MAGBY, Species.TORKOAL, Species.GROWLITHE, Species.BALTOY ], [TrainerPoolTier.UNCOMMON]: [ Species.SOLROCK, Species.HIPPOPOTAS, Species.SANDACONDA, Species.PHANPY, Species.ROLYCOLY, Species.GLIGAR, Species.RHYHORN, Species.HEATMOR ], - [TrainerPoolTier.RARE]: [ Species.TRAPINCH, Species.LILEEP, Species.ANORITH, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON ], + [TrainerPoolTier.RARE]: [ Species.TRAPINCH, Species.LILEEP, Species.ANORITH, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ], [TrainerPoolTier.SUPER_RARE]: [ Species.CAPSAKID, Species.CHARCADET ] }), [TrainerType.TABITHA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("magma_admin", "magma", [ Species.CAMERUPT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), @@ -1540,9 +1540,9 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.FLARE_GRUNT]: new TrainerConfig(++t).setHasGenders("Flare Grunt Female").setHasDouble("Flare Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK, Species.SCATTERBUG, Species.ESPURR ], - [TrainerPoolTier.UNCOMMON]: [ Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP ], + [TrainerPoolTier.UNCOMMON]: [ Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP, Species.FOONGUS ], [TrainerPoolTier.RARE]: [ Species.LITWICK, Species.SNEASEL, Species.PAWNIARD, Species.SLIGGOO ], - [TrainerPoolTier.SUPER_RARE]: [ Species.NOIVERN, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG ] + [TrainerPoolTier.SUPER_RARE]: [ Species.NOIBAT, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG ] }), [TrainerType.BRYONY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin_female", "flare", [ Species.LIEPARD ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), [TrainerType.XEROSIC]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin", "flare", [ Species.MALAMAR ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), @@ -1893,7 +1893,10 @@ export const trainerConfigs: TrainerConfigs = { }), [TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma") - .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN, Species.ALOLA_PERSIAN ])) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN, Species.ALOLA_PERSIAN ], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.gender = Gender.MALE; + })) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.DUGTRIO, Species.ALOLA_DUGTRIO ])) .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.HONCHKROW ])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.NIDOKING, Species.NIDOQUEEN ])) @@ -1945,6 +1948,7 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.ULTRA_BALL; p.formIndex = 1; // Mega Camerupt p.generateName(); + p.gender = Gender.MALE; })), [TrainerType.MAXIE_2]: new TrainerConfig(++t).setName("Maxie").initForEvilTeamLeader("Magma Boss", [], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SOLROCK, Species.TYPHLOSION ], TrainerSlot.TRAINER, true, p => { @@ -1967,6 +1971,7 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.ULTRA_BALL; p.formIndex = 1; // Mega Camerupt p.generateName(); + p.gender = Gender.MALE; })) .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GROUDON ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); @@ -1985,6 +1990,7 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.ULTRA_BALL; p.formIndex = 1; // Mega Sharpedo p.generateName(); + p.gender = Gender.MALE; })), [TrainerType.ARCHIE_2]: new TrainerConfig(++t).setName("Archie").initForEvilTeamLeader("Aqua Boss", [], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.EMPOLEON, Species.LUDICOLO ], TrainerSlot.TRAINER, true, p => { @@ -2010,6 +2016,7 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.ULTRA_BALL; p.formIndex = 1; // Mega Sharpedo p.generateName(); + p.gender = Gender.MALE; })) .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYOGRE ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); @@ -2031,6 +2038,7 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; + p.gender = Gender.MALE; })), [TrainerType.CYRUS_2]: new TrainerConfig(++t).setName("Cyrus").initForEvilTeamLeader("Galactic Boss", [], true).setMixedBattleBgm("battle_galactic_boss").setVictoryBgm("victory_team_plasma") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.AZELF, Species.UXIE, Species.MESPRIT ], TrainerSlot.TRAINER, true, p => { @@ -2049,6 +2057,7 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; + p.gender = Gender.MALE; })) .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.DARKRAI ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); @@ -2065,6 +2074,7 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; + p.gender = Gender.MALE; })), [TrainerType.GHETSIS_2]: new TrainerConfig(++t).setName("Ghetsis").initForEvilTeamLeader("Plasma Boss", [], true).setMixedBattleBgm("battle_plasma_boss").setVictoryBgm("victory_team_plasma") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GENESECT ], TrainerSlot.TRAINER, true, p => { @@ -2084,6 +2094,11 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; + if (p.species.speciesId === Species.HYDREIGON) { + p.gender = Gender.MALE; + } else if (p.species.speciesId === Species.IRON_JUGULIS) { + p.gender = Gender.GENDERLESS; + } })) .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYUREM ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); @@ -2105,6 +2120,7 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.ULTRA_BALL; p.formIndex = 1; // Mega Gyarados p.generateName(); + p.gender = Gender.MALE; })), [TrainerType.LYSANDRE_2]: new TrainerConfig(++t).setName("Lysandre").initForEvilTeamLeader("Flare Boss", [], true).setMixedBattleBgm("battle_flare_boss").setVictoryBgm("victory_team_plasma") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SCREAM_TAIL, Species.FLUTTER_MANE ], TrainerSlot.TRAINER, true, p => { @@ -2124,6 +2140,7 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.ULTRA_BALL; p.formIndex = 1; // Mega Gyardos p.generateName(); + p.gender = Gender.MALE; })) .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.YVELTAL ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); @@ -2131,7 +2148,10 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.MASTER_BALL; })), [TrainerType.LUSAMINE]: new TrainerConfig(++t).setName("Lusamine").initForEvilTeamLeader("Aether Boss", []).setMixedBattleBgm("battle_aether_boss").setVictoryBgm("victory_team_plasma") - .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLEFABLE ])) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLEFABLE ], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.gender = Gender.FEMALE; + })) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.LILLIGANT, Species.HISUI_LILLIGANT ])) .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.MILOTIC, Species.PRIMARINA ])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GALAR_SLOWBRO, Species.GALAR_SLOWKING ])) @@ -2148,7 +2168,10 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.ROGUE_BALL; })) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.MILOTIC, Species.PRIMARINA ])) - .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.CLEFABLE ])) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.CLEFABLE ], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.gender = Gender.FEMALE; + })) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.STAKATAKA, Species.CELESTEELA, Species.GUZZLORD ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ROGUE_BALL; @@ -2191,6 +2214,7 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GOLISOPOD ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.generateAndPopulateMoveset(); + p.gender = Gender.MALE; p.pokeball = PokeballType.ULTRA_BALL; })), [TrainerType.GUZMA_2]: new TrainerConfig(++t).setName("Guzma").initForEvilTeamLeader("Skull Boss", [], true).setMixedBattleBgm("battle_skull_boss").setVictoryBgm("victory_team_plasma") @@ -2198,6 +2222,7 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.generateAndPopulateMoveset(); p.abilityIndex = 2; //Anticipation + p.gender = Gender.MALE; p.pokeball = PokeballType.ULTRA_BALL; })) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ], TrainerSlot.TRAINER, true, p => { @@ -2239,6 +2264,7 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = 1; // G-Max Copperajah p.generateName(); p.pokeball = PokeballType.ULTRA_BALL; + p.gender = Gender.FEMALE; })), [TrainerType.ROSE_2]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", [], true).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ], TrainerSlot.TRAINER, true, p => { @@ -2262,6 +2288,7 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = 1; // G-Max Copperajah p.generateName(); p.pokeball = PokeballType.ULTRA_BALL; + p.gender = Gender.FEMALE; })), [TrainerType.PENNY]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", []).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma") .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VAPOREON, Species.JOLTEON, Species.FLAREON ])) @@ -2275,8 +2302,9 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = Utils.randSeedInt(5, 1); // Heat, Wash, Frost, Fan, or Mow })) .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); p.abilityIndex = 2; // Pixilate + p.generateAndPopulateMoveset(); + p.gender = Gender.FEMALE; })) .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); @@ -2290,20 +2318,21 @@ export const trainerConfigs: TrainerConfigs = { return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct? }), [TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma") - .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => { + .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); - p.formIndex = Utils.randSeedInt(5, 1); //Random Starmobile form + p.abilityIndex = 2; // Pixilate p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ULTRA_BALL; + p.gender = Gender.FEMALE; })) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ENTEI, Species.RAIKOU, Species.SUICUNE ], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; })) .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.WALKING_WAKE, Species.GOUGING_FIRE, Species.RAGING_BOLT ])) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => { + .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => { + p.formIndex = Utils.randSeedInt(5, 1); //Random Starmobile form p.generateAndPopulateMoveset(); - p.abilityIndex = 2; // Pixilate + p.pokeball = PokeballType.ROGUE_BALL; })) .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); @@ -2318,7 +2347,7 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.MASTER_BALL; })) .setGenModifiersFunc(party => { - const teraPokemon = party[3]; + const teraPokemon = party[0]; return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct? }), [TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true) diff --git a/src/data/type.ts b/src/data/type.ts index 47bea8dd72b..483ec068d3c 100644 --- a/src/data/type.ts +++ b/src/data/type.ts @@ -29,260 +29,260 @@ export function getTypeDamageMultiplier(attackType: Type, defType: Type): TypeDa } switch (defType) { - case Type.NORMAL: - switch (attackType) { - case Type.FIGHTING: - return 2; - case Type.GHOST: - return 0; - default: - return 1; - } - case Type.FIGHTING: - switch (attackType) { - case Type.FLYING: - case Type.PSYCHIC: - case Type.FAIRY: - return 2; - case Type.ROCK: - case Type.BUG: - case Type.DARK: - return 0.5; - default: - return 1; - } - case Type.FLYING: - switch (attackType) { - case Type.ROCK: - case Type.ELECTRIC: - case Type.ICE: - return 2; - case Type.FIGHTING: - case Type.BUG: - case Type.GRASS: - return 0.5; - case Type.GROUND: - return 0; - default: - return 1; - } - case Type.POISON: - switch (attackType) { - case Type.GROUND: - case Type.PSYCHIC: - return 2; - case Type.FIGHTING: - case Type.POISON: - case Type.BUG: - case Type.GRASS: - case Type.FAIRY: - return 0.5; - default: - return 1; - } - case Type.GROUND: - switch (attackType) { - case Type.WATER: - case Type.GRASS: - case Type.ICE: - return 2; - case Type.POISON: - case Type.ROCK: - return 0.5; - case Type.ELECTRIC: - return 0; - default: - return 1; - } - case Type.ROCK: - switch (attackType) { - case Type.FIGHTING: - case Type.GROUND: - case Type.STEEL: - case Type.WATER: - case Type.GRASS: - return 2; case Type.NORMAL: - case Type.FLYING: - case Type.POISON: - case Type.FIRE: - return 0.5; - default: - return 1; - } - case Type.BUG: - switch (attackType) { - case Type.FLYING: - case Type.ROCK: - case Type.FIRE: - return 2; + switch (attackType) { + case Type.FIGHTING: + return 2; + case Type.GHOST: + return 0; + default: + return 1; + } case Type.FIGHTING: + switch (attackType) { + case Type.FLYING: + case Type.PSYCHIC: + case Type.FAIRY: + return 2; + case Type.ROCK: + case Type.BUG: + case Type.DARK: + return 0.5; + default: + return 1; + } + case Type.FLYING: + switch (attackType) { + case Type.ROCK: + case Type.ELECTRIC: + case Type.ICE: + return 2; + case Type.FIGHTING: + case Type.BUG: + case Type.GRASS: + return 0.5; + case Type.GROUND: + return 0; + default: + return 1; + } + case Type.POISON: + switch (attackType) { + case Type.GROUND: + case Type.PSYCHIC: + return 2; + case Type.FIGHTING: + case Type.POISON: + case Type.BUG: + case Type.GRASS: + case Type.FAIRY: + return 0.5; + default: + return 1; + } case Type.GROUND: - case Type.GRASS: - return 0.5; - default: - return 1; - } - case Type.GHOST: - switch (attackType) { + switch (attackType) { + case Type.WATER: + case Type.GRASS: + case Type.ICE: + return 2; + case Type.POISON: + case Type.ROCK: + return 0.5; + case Type.ELECTRIC: + return 0; + default: + return 1; + } + case Type.ROCK: + switch (attackType) { + case Type.FIGHTING: + case Type.GROUND: + case Type.STEEL: + case Type.WATER: + case Type.GRASS: + return 2; + case Type.NORMAL: + case Type.FLYING: + case Type.POISON: + case Type.FIRE: + return 0.5; + default: + return 1; + } + case Type.BUG: + switch (attackType) { + case Type.FLYING: + case Type.ROCK: + case Type.FIRE: + return 2; + case Type.FIGHTING: + case Type.GROUND: + case Type.GRASS: + return 0.5; + default: + return 1; + } case Type.GHOST: - case Type.DARK: - return 2; - case Type.POISON: - case Type.BUG: - return 0.5; - case Type.NORMAL: - case Type.FIGHTING: - return 0; - default: - return 1; - } - case Type.STEEL: - switch (attackType) { - case Type.FIGHTING: - case Type.GROUND: - case Type.FIRE: - return 2; - case Type.NORMAL: - case Type.FLYING: - case Type.ROCK: - case Type.BUG: + switch (attackType) { + case Type.GHOST: + case Type.DARK: + return 2; + case Type.POISON: + case Type.BUG: + return 0.5; + case Type.NORMAL: + case Type.FIGHTING: + return 0; + default: + return 1; + } case Type.STEEL: + switch (attackType) { + case Type.FIGHTING: + case Type.GROUND: + case Type.FIRE: + return 2; + case Type.NORMAL: + case Type.FLYING: + case Type.ROCK: + case Type.BUG: + case Type.STEEL: + case Type.GRASS: + case Type.PSYCHIC: + case Type.ICE: + case Type.DRAGON: + case Type.FAIRY: + return 0.5; + case Type.POISON: + return 0; + default: + return 1; + } + case Type.FIRE: + switch (attackType) { + case Type.GROUND: + case Type.ROCK: + case Type.WATER: + return 2; + case Type.BUG: + case Type.STEEL: + case Type.FIRE: + case Type.GRASS: + case Type.ICE: + case Type.FAIRY: + return 0.5; + default: + return 1; + } + case Type.WATER: + switch (attackType) { + case Type.GRASS: + case Type.ELECTRIC: + return 2; + case Type.STEEL: + case Type.FIRE: + case Type.WATER: + case Type.ICE: + return 0.5; + default: + return 1; + } case Type.GRASS: + switch (attackType) { + case Type.FLYING: + case Type.POISON: + case Type.BUG: + case Type.FIRE: + case Type.ICE: + return 2; + case Type.GROUND: + case Type.WATER: + case Type.GRASS: + case Type.ELECTRIC: + return 0.5; + default: + return 1; + } + case Type.ELECTRIC: + switch (attackType) { + case Type.GROUND: + return 2; + case Type.FLYING: + case Type.STEEL: + case Type.ELECTRIC: + return 0.5; + default: + return 1; + } case Type.PSYCHIC: + switch (attackType) { + case Type.BUG: + case Type.GHOST: + case Type.DARK: + return 2; + case Type.FIGHTING: + case Type.PSYCHIC: + return 0.5; + default: + return 1; + } case Type.ICE: + switch (attackType) { + case Type.FIGHTING: + case Type.ROCK: + case Type.STEEL: + case Type.FIRE: + return 2; + case Type.ICE: + return 0.5; + default: + return 1; + } case Type.DRAGON: - case Type.FAIRY: - return 0.5; - case Type.POISON: - return 0; - default: - return 1; - } - case Type.FIRE: - switch (attackType) { - case Type.GROUND: - case Type.ROCK: - case Type.WATER: - return 2; - case Type.BUG: - case Type.STEEL: - case Type.FIRE: - case Type.GRASS: - case Type.ICE: - case Type.FAIRY: - return 0.5; - default: - return 1; - } - case Type.WATER: - switch (attackType) { - case Type.GRASS: - case Type.ELECTRIC: - return 2; - case Type.STEEL: - case Type.FIRE: - case Type.WATER: - case Type.ICE: - return 0.5; - default: - return 1; - } - case Type.GRASS: - switch (attackType) { - case Type.FLYING: - case Type.POISON: - case Type.BUG: - case Type.FIRE: - case Type.ICE: - return 2; - case Type.GROUND: - case Type.WATER: - case Type.GRASS: - case Type.ELECTRIC: - return 0.5; - default: - return 1; - } - case Type.ELECTRIC: - switch (attackType) { - case Type.GROUND: - return 2; - case Type.FLYING: - case Type.STEEL: - case Type.ELECTRIC: - return 0.5; - default: - return 1; - } - case Type.PSYCHIC: - switch (attackType) { - case Type.BUG: - case Type.GHOST: + switch (attackType) { + case Type.ICE: + case Type.DRAGON: + case Type.FAIRY: + return 2; + case Type.FIRE: + case Type.WATER: + case Type.GRASS: + case Type.ELECTRIC: + return 0.5; + default: + return 1; + } case Type.DARK: - return 2; - case Type.FIGHTING: - case Type.PSYCHIC: - return 0.5; - default: - return 1; - } - case Type.ICE: - switch (attackType) { - case Type.FIGHTING: - case Type.ROCK: - case Type.STEEL: - case Type.FIRE: - return 2; - case Type.ICE: - return 0.5; - default: - return 1; - } - case Type.DRAGON: - switch (attackType) { - case Type.ICE: - case Type.DRAGON: + switch (attackType) { + case Type.FIGHTING: + case Type.BUG: + case Type.FAIRY: + return 2; + case Type.GHOST: + case Type.DARK: + return 0.5; + case Type.PSYCHIC: + return 0; + default: + return 1; + } case Type.FAIRY: - return 2; - case Type.FIRE: - case Type.WATER: - case Type.GRASS: - case Type.ELECTRIC: - return 0.5; - default: + switch (attackType) { + case Type.POISON: + case Type.STEEL: + return 2; + case Type.FIGHTING: + case Type.BUG: + case Type.DARK: + return 0.5; + case Type.DRAGON: + return 0; + default: + return 1; + } + case Type.STELLAR: return 1; - } - case Type.DARK: - switch (attackType) { - case Type.FIGHTING: - case Type.BUG: - case Type.FAIRY: - return 2; - case Type.GHOST: - case Type.DARK: - return 0.5; - case Type.PSYCHIC: - return 0; - default: - return 1; - } - case Type.FAIRY: - switch (attackType) { - case Type.POISON: - case Type.STEEL: - return 2; - case Type.FIGHTING: - case Type.BUG: - case Type.DARK: - return 0.5; - case Type.DRAGON: - return 0; - default: - return 1; - } - case Type.STELLAR: - return 1; } return 1; @@ -295,86 +295,86 @@ export function getTypeDamageMultiplier(attackType: Type, defType: Type): TypeDa export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, side: "defense" | "offense"): string | undefined { if (side === "offense") { switch (multiplier) { - case 0: - return "#929292"; - case 0.125: - return "#FF5500"; - case 0.25: - return "#FF7400"; - case 0.5: - return "#FE8E00"; - case 1: - return undefined; - case 2: - return "#4AA500"; - case 4: - return "#4BB400"; - case 8: - return "#52C200"; + case 0: + return "#929292"; + case 0.125: + return "#FF5500"; + case 0.25: + return "#FF7400"; + case 0.5: + return "#FE8E00"; + case 1: + return undefined; + case 2: + return "#4AA500"; + case 4: + return "#4BB400"; + case 8: + return "#52C200"; } } else if (side === "defense") { switch (multiplier) { - case 0: - return "#B1B100"; - case 0.125: - return "#2DB4FF"; - case 0.25: - return "#00A4FF"; - case 0.5: - return "#0093FF"; - case 1: - return undefined; - case 2: - return "#FE8E00"; - case 4: - return "#FF7400"; - case 8: - return "#FF5500"; + case 0: + return "#B1B100"; + case 0.125: + return "#2DB4FF"; + case 0.25: + return "#00A4FF"; + case 0.5: + return "#0093FF"; + case 1: + return undefined; + case 2: + return "#FE8E00"; + case 4: + return "#FF7400"; + case 8: + return "#FF5500"; } } } export function getTypeRgb(type: Type): [ integer, integer, integer ] { switch (type) { - case Type.NORMAL: - return [ 168, 168, 120 ]; - case Type.FIGHTING: - return [ 192, 48, 40 ]; - case Type.FLYING: - return [ 168, 144, 240 ]; - case Type.POISON: - return [ 160, 64, 160 ]; - case Type.GROUND: - return [ 224, 192, 104 ]; - case Type.ROCK: - return [ 184, 160, 56 ]; - case Type.BUG: - return [ 168, 184, 32 ]; - case Type.GHOST: - return [ 112, 88, 152 ]; - case Type.STEEL: - return [ 184, 184, 208 ]; - case Type.FIRE: - return [ 240, 128, 48 ]; - case Type.WATER: - return [ 104, 144, 240 ]; - case Type.GRASS: - return [ 120, 200, 80 ]; - case Type.ELECTRIC: - return [ 248, 208, 48 ]; - case Type.PSYCHIC: - return [ 248, 88, 136 ]; - case Type.ICE: - return [ 152, 216, 216 ]; - case Type.DRAGON: - return [ 112, 56, 248 ]; - case Type.DARK: - return [ 112, 88, 72 ]; - case Type.FAIRY: - return [ 232, 136, 200 ]; - case Type.STELLAR: - return [ 255, 255, 255 ]; - default: - return [ 0, 0, 0 ]; + case Type.NORMAL: + return [ 168, 168, 120 ]; + case Type.FIGHTING: + return [ 192, 48, 40 ]; + case Type.FLYING: + return [ 168, 144, 240 ]; + case Type.POISON: + return [ 160, 64, 160 ]; + case Type.GROUND: + return [ 224, 192, 104 ]; + case Type.ROCK: + return [ 184, 160, 56 ]; + case Type.BUG: + return [ 168, 184, 32 ]; + case Type.GHOST: + return [ 112, 88, 152 ]; + case Type.STEEL: + return [ 184, 184, 208 ]; + case Type.FIRE: + return [ 240, 128, 48 ]; + case Type.WATER: + return [ 104, 144, 240 ]; + case Type.GRASS: + return [ 120, 200, 80 ]; + case Type.ELECTRIC: + return [ 248, 208, 48 ]; + case Type.PSYCHIC: + return [ 248, 88, 136 ]; + case Type.ICE: + return [ 152, 216, 216 ]; + case Type.DRAGON: + return [ 112, 56, 248 ]; + case Type.DARK: + return [ 112, 88, 72 ]; + case Type.FAIRY: + return [ 232, 136, 200 ]; + case Type.STELLAR: + return [ 255, 255, 255 ]; + default: + return [ 0, 0, 0 ]; } } diff --git a/src/data/variant.ts b/src/data/variant.ts index b7a01a4be89..13869635f1e 100644 --- a/src/data/variant.ts +++ b/src/data/variant.ts @@ -10,22 +10,22 @@ export const variantColorCache = {}; export function getVariantTint(variant: Variant): integer { switch (variant) { - case 0: - return 0xf8c020; - case 1: - return 0x20f8f0; - case 2: - return 0xe81048; + case 0: + return 0xf8c020; + case 1: + return 0x20f8f0; + case 2: + return 0xe81048; } } export function getVariantIcon(variant: Variant): integer { switch (variant) { - case 0: - return VariantTier.STANDARD; - case 1: - return VariantTier.RARE; - case 2: - return VariantTier.EPIC; + case 0: + return VariantTier.STANDARD; + case 1: + return VariantTier.RARE; + case 2: + return VariantTier.EPIC; } } diff --git a/src/data/weather.ts b/src/data/weather.ts index 8dfa17c4ef8..20c03af77c8 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -33,10 +33,10 @@ export class Weather { isImmutable(): boolean { switch (this.weatherType) { - case WeatherType.HEAVY_RAIN: - case WeatherType.HARSH_SUN: - case WeatherType.STRONG_WINDS: - return true; + case WeatherType.HEAVY_RAIN: + case WeatherType.HARSH_SUN: + case WeatherType.STRONG_WINDS: + return true; } return false; @@ -44,9 +44,9 @@ export class Weather { isDamaging(): boolean { switch (this.weatherType) { - case WeatherType.SANDSTORM: - case WeatherType.HAIL: - return true; + case WeatherType.SANDSTORM: + case WeatherType.HAIL: + return true; } return false; @@ -54,10 +54,10 @@ export class Weather { isTypeDamageImmune(type: Type): boolean { switch (this.weatherType) { - case WeatherType.SANDSTORM: - return type === Type.GROUND || type === Type.ROCK || type === Type.STEEL; - case WeatherType.HAIL: - return type === Type.ICE; + case WeatherType.SANDSTORM: + return type === Type.GROUND || type === Type.ROCK || type === Type.STEEL; + case WeatherType.HAIL: + return type === Type.ICE; } return false; @@ -65,24 +65,24 @@ export class Weather { getAttackTypeMultiplier(attackType: Type): number { switch (this.weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - if (attackType === Type.FIRE) { - return 1.5; - } - if (attackType === Type.WATER) { - return 0.5; - } - break; - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - if (attackType === Type.FIRE) { - return 0.5; - } - if (attackType === Type.WATER) { - return 1.5; - } - break; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + if (attackType === Type.FIRE) { + return 1.5; + } + if (attackType === Type.WATER) { + return 0.5; + } + break; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + if (attackType === Type.FIRE) { + return 0.5; + } + if (attackType === Type.WATER) { + return 1.5; + } + break; } return 1; @@ -92,10 +92,10 @@ export class Weather { const moveType = user.getMoveType(move); switch (this.weatherType) { - case WeatherType.HARSH_SUN: - return move instanceof AttackMove && moveType === Type.WATER; - case WeatherType.HEAVY_RAIN: - return move instanceof AttackMove && moveType === Type.FIRE; + case WeatherType.HARSH_SUN: + return move instanceof AttackMove && moveType === Type.WATER; + case WeatherType.HEAVY_RAIN: + return move instanceof AttackMove && moveType === Type.FIRE; } return false; @@ -120,24 +120,24 @@ export class Weather { export function getWeatherStartMessage(weatherType: WeatherType): string | null { switch (weatherType) { - case WeatherType.SUNNY: - return i18next.t("weather:sunnyStartMessage"); - case WeatherType.RAIN: - return i18next.t("weather:rainStartMessage"); - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormStartMessage"); - case WeatherType.HAIL: - return i18next.t("weather:hailStartMessage"); - case WeatherType.SNOW: - return i18next.t("weather:snowStartMessage"); - case WeatherType.FOG: - return i18next.t("weather:fogStartMessage"); - case WeatherType.HEAVY_RAIN: - return i18next.t("weather:heavyRainStartMessage"); - case WeatherType.HARSH_SUN: - return i18next.t("weather:harshSunStartMessage"); - case WeatherType.STRONG_WINDS: - return i18next.t("weather:strongWindsStartMessage"); + case WeatherType.SUNNY: + return i18next.t("weather:sunnyStartMessage"); + case WeatherType.RAIN: + return i18next.t("weather:rainStartMessage"); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormStartMessage"); + case WeatherType.HAIL: + return i18next.t("weather:hailStartMessage"); + case WeatherType.SNOW: + return i18next.t("weather:snowStartMessage"); + case WeatherType.FOG: + return i18next.t("weather:fogStartMessage"); + case WeatherType.HEAVY_RAIN: + return i18next.t("weather:heavyRainStartMessage"); + case WeatherType.HARSH_SUN: + return i18next.t("weather:harshSunStartMessage"); + case WeatherType.STRONG_WINDS: + return i18next.t("weather:strongWindsStartMessage"); } return null; @@ -145,24 +145,24 @@ export function getWeatherStartMessage(weatherType: WeatherType): string | null export function getWeatherLapseMessage(weatherType: WeatherType): string | null { switch (weatherType) { - case WeatherType.SUNNY: - return i18next.t("weather:sunnyLapseMessage"); - case WeatherType.RAIN: - return i18next.t("weather:rainLapseMessage"); - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormLapseMessage"); - case WeatherType.HAIL: - return i18next.t("weather:hailLapseMessage"); - case WeatherType.SNOW: - return i18next.t("weather:snowLapseMessage"); - case WeatherType.FOG: - return i18next.t("weather:fogLapseMessage"); - case WeatherType.HEAVY_RAIN: - return i18next.t("weather:heavyRainLapseMessage"); - case WeatherType.HARSH_SUN: - return i18next.t("weather:harshSunLapseMessage"); - case WeatherType.STRONG_WINDS: - return i18next.t("weather:strongWindsLapseMessage"); + case WeatherType.SUNNY: + return i18next.t("weather:sunnyLapseMessage"); + case WeatherType.RAIN: + return i18next.t("weather:rainLapseMessage"); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormLapseMessage"); + case WeatherType.HAIL: + return i18next.t("weather:hailLapseMessage"); + case WeatherType.SNOW: + return i18next.t("weather:snowLapseMessage"); + case WeatherType.FOG: + return i18next.t("weather:fogLapseMessage"); + case WeatherType.HEAVY_RAIN: + return i18next.t("weather:heavyRainLapseMessage"); + case WeatherType.HARSH_SUN: + return i18next.t("weather:harshSunLapseMessage"); + case WeatherType.STRONG_WINDS: + return i18next.t("weather:strongWindsLapseMessage"); } return null; @@ -170,10 +170,10 @@ export function getWeatherLapseMessage(weatherType: WeatherType): string | null export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string | null { switch (weatherType) { - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); - case WeatherType.HAIL: - return i18next.t("weather:hailDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); + case WeatherType.HAIL: + return i18next.t("weather:hailDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); } return null; @@ -181,24 +181,24 @@ export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokem export function getWeatherClearMessage(weatherType: WeatherType): string | null { switch (weatherType) { - case WeatherType.SUNNY: - return i18next.t("weather:sunnyClearMessage"); - case WeatherType.RAIN: - return i18next.t("weather:rainClearMessage"); - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormClearMessage"); - case WeatherType.HAIL: - return i18next.t("weather:hailClearMessage"); - case WeatherType.SNOW: - return i18next.t("weather:snowClearMessage"); - case WeatherType.FOG: - return i18next.t("weather:fogClearMessage"); - case WeatherType.HEAVY_RAIN: - return i18next.t("weather:heavyRainClearMessage"); - case WeatherType.HARSH_SUN: - return i18next.t("weather:harshSunClearMessage"); - case WeatherType.STRONG_WINDS: - return i18next.t("weather:strongWindsClearMessage"); + case WeatherType.SUNNY: + return i18next.t("weather:sunnyClearMessage"); + case WeatherType.RAIN: + return i18next.t("weather:rainClearMessage"); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormClearMessage"); + case WeatherType.HAIL: + return i18next.t("weather:hailClearMessage"); + case WeatherType.SNOW: + return i18next.t("weather:snowClearMessage"); + case WeatherType.FOG: + return i18next.t("weather:fogClearMessage"); + case WeatherType.HEAVY_RAIN: + return i18next.t("weather:heavyRainClearMessage"); + case WeatherType.HARSH_SUN: + return i18next.t("weather:harshSunClearMessage"); + case WeatherType.STRONG_WINDS: + return i18next.t("weather:strongWindsClearMessage"); } return null; @@ -206,33 +206,33 @@ export function getWeatherClearMessage(weatherType: WeatherType): string | null export function getTerrainStartMessage(terrainType: TerrainType): string | null { switch (terrainType) { - case TerrainType.MISTY: - return i18next.t("terrain:mistyStartMessage"); - case TerrainType.ELECTRIC: - return i18next.t("terrain:electricStartMessage"); - case TerrainType.GRASSY: - return i18next.t("terrain:grassyStartMessage"); - case TerrainType.PSYCHIC: - return i18next.t("terrain:psychicStartMessage"); - default: - console.warn("getTerrainStartMessage not defined. Using default null"); - return null; + case TerrainType.MISTY: + return i18next.t("terrain:mistyStartMessage"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electricStartMessage"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassyStartMessage"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychicStartMessage"); + default: + console.warn("getTerrainStartMessage not defined. Using default null"); + return null; } } export function getTerrainClearMessage(terrainType: TerrainType): string | null { switch (terrainType) { - case TerrainType.MISTY: - return i18next.t("terrain:mistyClearMessage"); - case TerrainType.ELECTRIC: - return i18next.t("terrain:electricClearMessage"); - case TerrainType.GRASSY: - return i18next.t("terrain:grassyClearMessage"); - case TerrainType.PSYCHIC: - return i18next.t("terrain:psychicClearMessage"); - default: - console.warn("getTerrainClearMessage not defined. Using default null"); - return null; + case TerrainType.MISTY: + return i18next.t("terrain:mistyClearMessage"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electricClearMessage"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassyClearMessage"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychicClearMessage"); + default: + console.warn("getTerrainClearMessage not defined. Using default null"); + return null; } } @@ -252,126 +252,126 @@ export function getRandomWeatherType(arena: any /* Importing from arena causes a let weatherPool: WeatherPoolEntry[] = []; const hasSun = arena.getTimeOfDay() < 2; switch (arena.biomeType) { - case Biome.GRASS: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 7 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 3 }); - } - break; - case Biome.TALL_GRASS: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 5 }, - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 8 }); - } - break; - case Biome.FOREST: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 5 } - ]; - break; - case Biome.SEA: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.RAIN, weight: 12 } - ]; - break; - case Biome.SWAMP: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.RAIN, weight: 4 }, - { weatherType: WeatherType.FOG, weight: 1 } - ]; - break; - case Biome.BEACH: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 3 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); - } - break; - case Biome.LAKE: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 10 }, - { weatherType: WeatherType.RAIN, weight: 5 }, - { weatherType: WeatherType.FOG, weight: 1 } - ]; - break; - case Biome.SEABED: - weatherPool = [ - { weatherType: WeatherType.RAIN, weight: 1 } - ]; - break; - case Biome.BADLANDS: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.SANDSTORM, weight: 2 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); - } - break; - case Biome.DESERT: - weatherPool = [ - { weatherType: WeatherType.SANDSTORM, weight: 2 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); - } - break; - case Biome.ICE_CAVE: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.SNOW, weight: 4 }, - { weatherType: WeatherType.HAIL, weight: 1 } - ]; - break; - case Biome.MEADOW: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 2 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); - } - case Biome.VOLCANO: - weatherPool = [ - { weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE, weight: 1 } - ]; - break; - case Biome.GRAVEYARD: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.FOG, weight: 1 } - ]; - break; - case Biome.JUNGLE: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 2 } - ]; - break; - case Biome.SNOWY_FOREST: - weatherPool = [ - { weatherType: WeatherType.SNOW, weight: 7 }, - { weatherType: WeatherType.HAIL, weight: 1 } - ]; - break; - case Biome.ISLAND: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 5 }, - { weatherType: WeatherType.RAIN, weight: 1 }, - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); - } - break; + case Biome.GRASS: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 7 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 3 }); + } + break; + case Biome.TALL_GRASS: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 5 }, + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 8 }); + } + break; + case Biome.FOREST: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 5 } + ]; + break; + case Biome.SEA: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.RAIN, weight: 12 } + ]; + break; + case Biome.SWAMP: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.RAIN, weight: 4 }, + { weatherType: WeatherType.FOG, weight: 1 } + ]; + break; + case Biome.BEACH: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 3 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); + } + break; + case Biome.LAKE: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 10 }, + { weatherType: WeatherType.RAIN, weight: 5 }, + { weatherType: WeatherType.FOG, weight: 1 } + ]; + break; + case Biome.SEABED: + weatherPool = [ + { weatherType: WeatherType.RAIN, weight: 1 } + ]; + break; + case Biome.BADLANDS: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.SANDSTORM, weight: 2 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); + } + break; + case Biome.DESERT: + weatherPool = [ + { weatherType: WeatherType.SANDSTORM, weight: 2 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); + } + break; + case Biome.ICE_CAVE: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.SNOW, weight: 4 }, + { weatherType: WeatherType.HAIL, weight: 1 } + ]; + break; + case Biome.MEADOW: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 2 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); + } + case Biome.VOLCANO: + weatherPool = [ + { weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE, weight: 1 } + ]; + break; + case Biome.GRAVEYARD: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.FOG, weight: 1 } + ]; + break; + case Biome.JUNGLE: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 2 } + ]; + break; + case Biome.SNOWY_FOREST: + weatherPool = [ + { weatherType: WeatherType.SNOW, weight: 7 }, + { weatherType: WeatherType.HAIL, weight: 1 } + ]; + break; + case Biome.ISLAND: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 5 }, + { weatherType: WeatherType.RAIN, weight: 1 }, + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); + } + break; } if (weatherPool.length > 1) { diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index 43c849a78e0..680dedb93cc 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -54,7 +54,7 @@ export enum BattlerTagType { CURSED = "CURSED", CHARGED = "CHARGED", ROOSTED = "ROOSTED", - MAGNET_RISEN = "MAGNET_RISEN", + FLOATING = "FLOATING", MINIMIZED = "MINIMIZED", DESTINY_BOND = "DESTINY_BOND", CENTER_OF_ATTENTION = "CENTER_OF_ATTENTION", @@ -80,10 +80,12 @@ export enum BattlerTagType { DOUBLE_SHOCKED = "DOUBLE_SHOCKED", AUTOTOMIZED = "AUTOTOMIZED", MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON", + POWER_TRICK = "POWER_TRICK", HEAL_BLOCK = "HEAL_BLOCK", TORMENT = "TORMENT", TAUNT = "TAUNT", IMPRISON = "IMPRISON", SYRUP_BOMB = "SYRUP_BOMB", ELECTRIFIED = "ELECTRIFIED", + TELEKINESIS = "TELEKINESIS" } diff --git a/src/enums/egg-type.ts b/src/enums/egg-type.ts index d8d0facb020..901e60b3c76 100644 --- a/src/enums/egg-type.ts +++ b/src/enums/egg-type.ts @@ -1,6 +1,6 @@ export enum EggTier { COMMON, - GREAT, - ULTRA, - MASTER + RARE, + EPIC, + LEGENDARY } diff --git a/src/enums/stat.ts b/src/enums/stat.ts index a12d53e8559..6b3f7dc6d79 100644 --- a/src/enums/stat.ts +++ b/src/enums/stat.ts @@ -50,7 +50,7 @@ export function getStatStageChangeDescriptionKey(stages: number, isIncrease: boo return isIncrease ? "battle:statRose" : "battle:statFell"; } else if (stages === 2) { return isIncrease ? "battle:statSharplyRose" : "battle:statHarshlyFell"; - } else if (stages <= 6) { + } else if (stages > 2 && stages <= 6) { return isIncrease ? "battle:statRoseDrastically" : "battle:statSeverelyFell"; } return isIncrease ? "battle:statWontGoAnyHigher" : "battle:statWontGoAnyLower"; diff --git a/src/field/anims.ts b/src/field/anims.ts index 52a15aa4f20..c73c52027c5 100644 --- a/src/field/anims.ts +++ b/src/field/anims.ts @@ -4,21 +4,21 @@ import * as Utils from "../utils"; export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void { switch (pokeballType) { - case PokeballType.POKEBALL: - doDefaultPbOpenParticles(scene, x, y, 48); - break; - case PokeballType.GREAT_BALL: - doDefaultPbOpenParticles(scene, x, y, 96); - break; - case PokeballType.ULTRA_BALL: - doUbOpenParticles(scene, x, y, 8); - break; - case PokeballType.ROGUE_BALL: - doUbOpenParticles(scene, x, y, 10); - break; - case PokeballType.MASTER_BALL: - doMbOpenParticles(scene, x, y); - break; + case PokeballType.POKEBALL: + doDefaultPbOpenParticles(scene, x, y, 48); + break; + case PokeballType.GREAT_BALL: + doDefaultPbOpenParticles(scene, x, y, 96); + break; + case PokeballType.ULTRA_BALL: + doUbOpenParticles(scene, x, y, 8); + break; + case PokeballType.ROGUE_BALL: + doUbOpenParticles(scene, x, y, 10); + break; + case PokeballType.MASTER_BALL: + doMbOpenParticles(scene, x, y); + break; } } diff --git a/src/field/arena.ts b/src/field/arena.ts index 1e164903e9d..7bfdf9a0000 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -129,18 +129,18 @@ export class Arena { if (ret.subLegendary || ret.legendary || ret.mythical) { switch (true) { - case (ret.baseTotal >= 720): - regen = level < 90; - break; - case (ret.baseTotal >= 670): - regen = level < 70; - break; - case (ret.baseTotal >= 580): - regen = level < 50; - break; - default: - regen = level < 30; - break; + case (ret.baseTotal >= 720): + regen = level < 90; + break; + case (ret.baseTotal >= 670): + regen = level < 70; + break; + case (ret.baseTotal >= 580): + regen = level < 50; + break; + default: + regen = level < 30; + break; } } } @@ -177,41 +177,41 @@ export class Arena { getSpeciesFormIndex(species: PokemonSpecies): integer { switch (species.speciesId) { - case Species.BURMY: - case Species.WORMADAM: - switch (this.biomeType) { - case Biome.BEACH: - return 1; - case Biome.SLUM: - return 2; - } - break; - case Species.ROTOM: - switch (this.biomeType) { - case Biome.VOLCANO: - return 1; - case Biome.SEA: - return 2; - case Biome.ICE_CAVE: - return 3; - case Biome.MOUNTAIN: - return 4; - case Biome.TALL_GRASS: - return 5; - } - break; - case Species.LYCANROC: - const timeOfDay = this.getTimeOfDay(); - switch (timeOfDay) { - case TimeOfDay.DAY: - case TimeOfDay.DAWN: - return 0; - case TimeOfDay.DUSK: - return 2; - case TimeOfDay.NIGHT: - return 1; - } - break; + case Species.BURMY: + case Species.WORMADAM: + switch (this.biomeType) { + case Biome.BEACH: + return 1; + case Biome.SLUM: + return 2; + } + break; + case Species.ROTOM: + switch (this.biomeType) { + case Biome.VOLCANO: + return 1; + case Biome.SEA: + return 2; + case Biome.ICE_CAVE: + return 3; + case Biome.MOUNTAIN: + return 4; + case Biome.TALL_GRASS: + return 5; + } + break; + case Species.LYCANROC: + const timeOfDay = this.getTimeOfDay(); + switch (timeOfDay) { + case TimeOfDay.DAY: + case TimeOfDay.DAWN: + return 0; + case TimeOfDay.DUSK: + return 2; + case TimeOfDay.NIGHT: + return 1; + } + break; } return 0; @@ -219,70 +219,70 @@ export class Arena { getTypeForBiome() { switch (this.biomeType) { - case Biome.TOWN: - case Biome.PLAINS: - case Biome.METROPOLIS: - return Type.NORMAL; - case Biome.GRASS: - case Biome.TALL_GRASS: - return Type.GRASS; - case Biome.FOREST: - case Biome.JUNGLE: - return Type.BUG; - case Biome.SLUM: - case Biome.SWAMP: - return Type.POISON; - case Biome.SEA: - case Biome.BEACH: - case Biome.LAKE: - case Biome.SEABED: - return Type.WATER; - case Biome.MOUNTAIN: - return Type.FLYING; - case Biome.BADLANDS: - return Type.GROUND; - case Biome.CAVE: - case Biome.DESERT: - return Type.ROCK; - case Biome.ICE_CAVE: - case Biome.SNOWY_FOREST: - return Type.ICE; - case Biome.MEADOW: - case Biome.FAIRY_CAVE: - case Biome.ISLAND: - return Type.FAIRY; - case Biome.POWER_PLANT: - return Type.ELECTRIC; - case Biome.VOLCANO: - return Type.FIRE; - case Biome.GRAVEYARD: - case Biome.TEMPLE: - return Type.GHOST; - case Biome.DOJO: - case Biome.CONSTRUCTION_SITE: - return Type.FIGHTING; - case Biome.FACTORY: - case Biome.LABORATORY: - return Type.STEEL; - case Biome.RUINS: - case Biome.SPACE: - return Type.PSYCHIC; - case Biome.WASTELAND: - case Biome.END: - return Type.DRAGON; - case Biome.ABYSS: - return Type.DARK; - default: - return Type.UNKNOWN; + case Biome.TOWN: + case Biome.PLAINS: + case Biome.METROPOLIS: + return Type.NORMAL; + case Biome.GRASS: + case Biome.TALL_GRASS: + return Type.GRASS; + case Biome.FOREST: + case Biome.JUNGLE: + return Type.BUG; + case Biome.SLUM: + case Biome.SWAMP: + return Type.POISON; + case Biome.SEA: + case Biome.BEACH: + case Biome.LAKE: + case Biome.SEABED: + return Type.WATER; + case Biome.MOUNTAIN: + return Type.FLYING; + case Biome.BADLANDS: + return Type.GROUND; + case Biome.CAVE: + case Biome.DESERT: + return Type.ROCK; + case Biome.ICE_CAVE: + case Biome.SNOWY_FOREST: + return Type.ICE; + case Biome.MEADOW: + case Biome.FAIRY_CAVE: + case Biome.ISLAND: + return Type.FAIRY; + case Biome.POWER_PLANT: + return Type.ELECTRIC; + case Biome.VOLCANO: + return Type.FIRE; + case Biome.GRAVEYARD: + case Biome.TEMPLE: + return Type.GHOST; + case Biome.DOJO: + case Biome.CONSTRUCTION_SITE: + return Type.FIGHTING; + case Biome.FACTORY: + case Biome.LABORATORY: + return Type.STEEL; + case Biome.RUINS: + case Biome.SPACE: + return Type.PSYCHIC; + case Biome.WASTELAND: + case Biome.END: + return Type.DRAGON; + case Biome.ABYSS: + return Type.DARK; + default: + return Type.UNKNOWN; } } getBgTerrainColorRatioForBiome(): number { switch (this.biomeType) { - case Biome.SPACE: - return 1; - case Biome.END: - return 0; + case Biome.SPACE: + return 1; + case Biome.END: + return 0; } return 131 / 180; @@ -424,52 +424,52 @@ export class Arena { */ getTrainerChance(): integer { switch (this.biomeType) { - case Biome.METROPOLIS: - return 2; - case Biome.SLUM: - case Biome.BEACH: - case Biome.DOJO: - case Biome.CONSTRUCTION_SITE: - return 4; - case Biome.PLAINS: - case Biome.GRASS: - case Biome.LAKE: - case Biome.CAVE: - return 6; - case Biome.TALL_GRASS: - case Biome.FOREST: - case Biome.SEA: - case Biome.SWAMP: - case Biome.MOUNTAIN: - case Biome.BADLANDS: - case Biome.DESERT: - case Biome.MEADOW: - case Biome.POWER_PLANT: - case Biome.GRAVEYARD: - case Biome.FACTORY: - case Biome.SNOWY_FOREST: - return 8; - case Biome.ICE_CAVE: - case Biome.VOLCANO: - case Biome.RUINS: - case Biome.WASTELAND: - case Biome.JUNGLE: - case Biome.FAIRY_CAVE: - return 12; - case Biome.SEABED: - case Biome.ABYSS: - case Biome.SPACE: - case Biome.TEMPLE: - return 16; - default: - return 0; + case Biome.METROPOLIS: + return 2; + case Biome.SLUM: + case Biome.BEACH: + case Biome.DOJO: + case Biome.CONSTRUCTION_SITE: + return 4; + case Biome.PLAINS: + case Biome.GRASS: + case Biome.LAKE: + case Biome.CAVE: + return 6; + case Biome.TALL_GRASS: + case Biome.FOREST: + case Biome.SEA: + case Biome.SWAMP: + case Biome.MOUNTAIN: + case Biome.BADLANDS: + case Biome.DESERT: + case Biome.MEADOW: + case Biome.POWER_PLANT: + case Biome.GRAVEYARD: + case Biome.FACTORY: + case Biome.SNOWY_FOREST: + return 8; + case Biome.ICE_CAVE: + case Biome.VOLCANO: + case Biome.RUINS: + case Biome.WASTELAND: + case Biome.JUNGLE: + case Biome.FAIRY_CAVE: + return 12; + case Biome.SEABED: + case Biome.ABYSS: + case Biome.SPACE: + case Biome.TEMPLE: + return 16; + default: + return 0; } } getTimeOfDay(): TimeOfDay { switch (this.biomeType) { - case Biome.ABYSS: - return TimeOfDay.NIGHT; + case Biome.ABYSS: + return TimeOfDay.NIGHT; } const waveCycle = ((this.scene.currentBattle?.waveIndex || 0) + this.scene.waveCycleOffset) % 40; @@ -491,35 +491,35 @@ export class Arena { isOutside(): boolean { switch (this.biomeType) { - case Biome.SEABED: - case Biome.CAVE: - case Biome.ICE_CAVE: - case Biome.POWER_PLANT: - case Biome.DOJO: - case Biome.FACTORY: - case Biome.ABYSS: - case Biome.FAIRY_CAVE: - case Biome.TEMPLE: - case Biome.LABORATORY: - return false; - default: - return true; + case Biome.SEABED: + case Biome.CAVE: + case Biome.ICE_CAVE: + case Biome.POWER_PLANT: + case Biome.DOJO: + case Biome.FACTORY: + case Biome.ABYSS: + case Biome.FAIRY_CAVE: + case Biome.TEMPLE: + case Biome.LABORATORY: + return false; + default: + return true; } } overrideTint(): [integer, integer, integer] { switch (Overrides.ARENA_TINT_OVERRIDE) { - case TimeOfDay.DUSK: - return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; - break; - case (TimeOfDay.NIGHT): - return [ 64, 64, 64 ]; - break; - case TimeOfDay.DAWN: - case TimeOfDay.DAY: - default: - return [ 128, 128, 128 ]; - break; + case TimeOfDay.DUSK: + return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; + break; + case (TimeOfDay.NIGHT): + return [ 64, 64, 64 ]; + break; + case TimeOfDay.DAWN: + case TimeOfDay.DAY: + default: + return [ 128, 128, 128 ]; + break; } } @@ -528,10 +528,10 @@ export class Arena { return this.overrideTint(); } switch (this.biomeType) { - case Biome.ABYSS: - return [ 64, 64, 64 ]; - default: - return [ 128, 128, 128 ]; + case Biome.ABYSS: + return [ 64, 64, 64 ]; + default: + return [ 128, 128, 128 ]; } } @@ -544,8 +544,8 @@ export class Arena { } switch (this.biomeType) { - default: - return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; + default: + return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; } } @@ -554,10 +554,10 @@ export class Arena { return this.overrideTint(); } switch (this.biomeType) { - case Biome.ABYSS: - case Biome.SPACE: - case Biome.END: - return this.getDayTint(); + case Biome.ABYSS: + case Biome.SPACE: + case Biome.END: + return this.getDayTint(); } if (!this.isOutside()) { @@ -565,8 +565,8 @@ export class Arena { } switch (this.biomeType) { - default: - return [ 48, 48, 98 ]; + default: + return [ 48, 48, 98 ]; } } @@ -579,26 +579,28 @@ export class Arena { * Applies each `ArenaTag` in this Arena, based on which side (self, enemy, or both) is passed in as a parameter * @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply * @param side {@linkcode ArenaTagSide} which side's arena tags to apply + * @param simulated if `true`, this applies arena tags without changing game state * @param args array of parameters that the called upon tags may need */ - applyTagsForSide(tagType: ArenaTagType | Constructor, side: ArenaTagSide, ...args: unknown[]): void { + applyTagsForSide(tagType: ArenaTagType | Constructor, side: ArenaTagSide, simulated: boolean, ...args: unknown[]): void { let tags = typeof tagType === "string" ? this.tags.filter(t => t.tagType === tagType) : this.tags.filter(t => t instanceof tagType); if (side !== ArenaTagSide.BOTH) { tags = tags.filter(t => t.side === side); } - tags.forEach(t => t.apply(this, args)); + tags.forEach(t => t.apply(this, simulated, ...args)); } /** * Applies the specified tag to both sides (ie: both user and trainer's tag that match the Tag specified) * by calling {@linkcode applyTagsForSide()} * @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply + * @param simulated if `true`, this applies arena tags without changing game state * @param args array of parameters that the called upon tags may need */ - applyTags(tagType: ArenaTagType | Constructor, ...args: unknown[]): void { - this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args); + applyTags(tagType: ArenaTagType | Constructor, simulated: boolean, ...args: unknown[]): void { + this.applyTagsForSide(tagType, ArenaTagSide.BOTH, simulated, ...args); } /** @@ -745,77 +747,77 @@ export class Arena { getBgmLoopPoint(): number { switch (this.biomeType) { - case Biome.TOWN: - return 7.288; - case Biome.PLAINS: - return 17.485; - case Biome.GRASS: - return 1.995; - case Biome.TALL_GRASS: - return 9.608; - case Biome.METROPOLIS: - return 141.470; - case Biome.FOREST: - return 4.294; - case Biome.SEA: - return 0.024; - case Biome.SWAMP: - return 4.461; - case Biome.BEACH: - return 3.462; - case Biome.LAKE: - return 7.215; - case Biome.SEABED: - return 2.600; - case Biome.MOUNTAIN: - return 4.018; - case Biome.BADLANDS: - return 17.790; - case Biome.CAVE: - return 14.240; - case Biome.DESERT: - return 1.143; - case Biome.ICE_CAVE: - return 0.000; - case Biome.MEADOW: - return 3.891; - case Biome.POWER_PLANT: - return 9.447; - case Biome.VOLCANO: - return 17.637; - case Biome.GRAVEYARD: - return 3.232; - case Biome.DOJO: - return 6.205; - case Biome.FACTORY: - return 4.985; - case Biome.RUINS: - return 0.000; - case Biome.WASTELAND: - return 6.336; - case Biome.ABYSS: - return 5.130; - case Biome.SPACE: - return 20.036; - case Biome.CONSTRUCTION_SITE: - return 1.222; - case Biome.JUNGLE: - return 0.000; - case Biome.FAIRY_CAVE: - return 4.542; - case Biome.TEMPLE: - return 2.547; - case Biome.ISLAND: - return 2.751; - case Biome.LABORATORY: - return 114.862; - case Biome.SLUM: - return 0.000; - case Biome.SNOWY_FOREST: - return 3.047; - default: - console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`); - return 0; + case Biome.TOWN: + return 7.288; + case Biome.PLAINS: + return 17.485; + case Biome.GRASS: + return 1.995; + case Biome.TALL_GRASS: + return 9.608; + case Biome.METROPOLIS: + return 141.470; + case Biome.FOREST: + return 4.294; + case Biome.SEA: + return 0.024; + case Biome.SWAMP: + return 4.461; + case Biome.BEACH: + return 3.462; + case Biome.LAKE: + return 7.215; + case Biome.SEABED: + return 2.600; + case Biome.MOUNTAIN: + return 4.018; + case Biome.BADLANDS: + return 17.790; + case Biome.CAVE: + return 14.240; + case Biome.DESERT: + return 1.143; + case Biome.ICE_CAVE: + return 0.000; + case Biome.MEADOW: + return 3.891; + case Biome.POWER_PLANT: + return 9.447; + case Biome.VOLCANO: + return 17.637; + case Biome.GRAVEYARD: + return 3.232; + case Biome.DOJO: + return 6.205; + case Biome.FACTORY: + return 4.985; + case Biome.RUINS: + return 0.000; + case Biome.WASTELAND: + return 6.336; + case Biome.ABYSS: + return 5.130; + case Biome.SPACE: + return 20.036; + case Biome.CONSTRUCTION_SITE: + return 1.222; + case Biome.JUNGLE: + return 0.000; + case Biome.FAIRY_CAVE: + return 4.542; + case Biome.TEMPLE: + return 2.547; + case Biome.ISLAND: + return 2.751; + case Biome.LABORATORY: + return 114.862; + case Biome.SLUM: + return 0.000; + case Biome.SNOWY_FOREST: + return 3.047; + default: + console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`); + return 0; } } } @@ -826,32 +828,32 @@ export function getBiomeKey(biome: Biome): string { export function getBiomeHasProps(biomeType: Biome): boolean { switch (biomeType) { - case Biome.METROPOLIS: - case Biome.BEACH: - case Biome.LAKE: - case Biome.SEABED: - case Biome.MOUNTAIN: - case Biome.BADLANDS: - case Biome.CAVE: - case Biome.DESERT: - case Biome.ICE_CAVE: - case Biome.MEADOW: - case Biome.POWER_PLANT: - case Biome.VOLCANO: - case Biome.GRAVEYARD: - case Biome.FACTORY: - case Biome.RUINS: - case Biome.WASTELAND: - case Biome.ABYSS: - case Biome.CONSTRUCTION_SITE: - case Biome.JUNGLE: - case Biome.FAIRY_CAVE: - case Biome.TEMPLE: - case Biome.SNOWY_FOREST: - case Biome.ISLAND: - case Biome.LABORATORY: - case Biome.END: - return true; + case Biome.METROPOLIS: + case Biome.BEACH: + case Biome.LAKE: + case Biome.SEABED: + case Biome.MOUNTAIN: + case Biome.BADLANDS: + case Biome.CAVE: + case Biome.DESERT: + case Biome.ICE_CAVE: + case Biome.MEADOW: + case Biome.POWER_PLANT: + case Biome.VOLCANO: + case Biome.GRAVEYARD: + case Biome.FACTORY: + case Biome.RUINS: + case Biome.WASTELAND: + case Biome.ABYSS: + case Biome.CONSTRUCTION_SITE: + case Biome.JUNGLE: + case Biome.FAIRY_CAVE: + case Biome.TEMPLE: + case Biome.SNOWY_FOREST: + case Biome.ISLAND: + case Biome.LABORATORY: + case Biome.END: + return true; } return false; diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index ae0692da342..4ddcd2d3ee7 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -29,21 +29,21 @@ export default class DamageNumberHandler { let [ textColor, shadowColor ] : TextAndShadowArr = [ null, null ]; switch (result) { - case HitResult.SUPER_EFFECTIVE: - [ textColor, shadowColor ] = [ "#f8d030", "#b8a038" ]; - break; - case HitResult.NOT_VERY_EFFECTIVE: - [ textColor, shadowColor ] = [ "#f08030", "#c03028" ]; - break; - case HitResult.ONE_HIT_KO: - [ textColor, shadowColor ] = [ "#a040a0", "#483850" ]; - break; - case HitResult.HEAL: - [ textColor, shadowColor ] = [ "#78c850", "#588040" ]; - break; - default: - [ textColor, shadowColor ] = [ "#ffffff", "#636363" ]; - break; + case HitResult.SUPER_EFFECTIVE: + [ textColor, shadowColor ] = [ "#f8d030", "#b8a038" ]; + break; + case HitResult.NOT_VERY_EFFECTIVE: + [ textColor, shadowColor ] = [ "#f08030", "#c03028" ]; + break; + case HitResult.ONE_HIT_KO: + [ textColor, shadowColor ] = [ "#a040a0", "#483850" ]; + break; + case HitResult.HEAL: + [ textColor, shadowColor ] = [ "#78c850", "#588040" ]; + break; + default: + [ textColor, shadowColor ] = [ "#ffffff", "#636363" ]; + break; } if (textColor) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a0ad4e8a52f..0ee879ebf97 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5,7 +5,7 @@ import { variantData } from "#app/data/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info"; import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; -import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; +import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "#app/utils"; @@ -19,7 +19,7 @@ import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { Status, StatusEffect, getRandomStatus } from "#app/data/status-effect"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; -import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } from "../data/battler-tags"; +import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags"; import { WeatherType } from "#app/data/weather"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "#app/data/ability"; @@ -62,7 +62,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph import { Challenges } from "#enums/challenges"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { SwitchType } from "#enums/switch-type"; import { SpeciesFormKey } from "#enums/species-form-key"; import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates"; @@ -114,7 +114,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public fusionVariant: Variant; public fusionGender: Gender; public fusionLuck: integer; - public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData | null; + public fusionCustomPokemonData: CustomPokemonData | null; private summonDataPrimer: PokemonSummonData | null; @@ -122,7 +122,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public battleData: PokemonBattleData; public battleSummonData: PokemonBattleSummonData; public turnData: PokemonTurnData; - public mysteryEncounterPokemonData: MysteryEncounterPokemonData; + public customPokemonData: CustomPokemonData; /** Used by Mystery Encounters to execute pokemon-specific logic (such as stat boosts) at start of battle */ public mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; @@ -193,7 +193,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.nature = dataSource.nature || 0 as Nature; this.nickname = dataSource.nickname; - this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1; this.moveset = dataSource.moveset; this.status = dataSource.status!; // TODO: is this bang correct? this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship; @@ -212,9 +211,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionVariant = dataSource.fusionVariant || 0; this.fusionGender = dataSource.fusionGender; this.fusionLuck = dataSource.fusionLuck; - this.fusionMysteryEncounterPokemonData = dataSource.fusionMysteryEncounterPokemonData; + this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData; this.usedTMs = dataSource.usedTMs ?? []; - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(dataSource.mysteryEncounterPokemonData); + this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData); } else { this.id = Utils.randSeedInt(4294967296); this.ivs = ivs || Utils.getIvsFromId(this.id); @@ -235,7 +234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.variant = this.shiny ? this.generateVariant() : 0; } - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + this.customPokemonData = new CustomPokemonData(); if (nature !== undefined) { this.setNature(nature); @@ -243,8 +242,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.generateNature(); } - this.natureOverride = -1; - this.friendship = species.baseFriendship; this.metLevel = level; this.metBiome = scene.currentBattle ? scene.arena.biomeType : -1; @@ -593,8 +590,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const formKey = this.getFormKey(); if (this.isMax() === true || formKey === "segin-starmobile" || formKey === "schedar-starmobile" || formKey === "navi-starmobile" || formKey === "ruchbah-starmobile" || formKey === "caph-starmobile") { return 1.5; - } else if (this.mysteryEncounterPokemonData.spriteScale > 0) { - return this.mysteryEncounterPokemonData.spriteScale; + } else if (this.customPokemonData.spriteScale > 0) { + return this.customPokemonData.spriteScale; } return 1; } @@ -680,12 +677,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getFieldPositionOffset(): [ number, number ] { switch (this.fieldPosition) { - case FieldPosition.CENTER: - return [ 0, 0 ]; - case FieldPosition.LEFT: - return [ -32, -8 ]; - case FieldPosition.RIGHT: - return [ 32, 0 ]; + case FieldPosition.CENTER: + return [ 0, 0 ]; + case FieldPosition.LEFT: + return [ -32, -8 ]; + case FieldPosition.RIGHT: + return [ 32, 0 ]; } } @@ -749,9 +746,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const relX = newOffset[0] - initialOffset[0]; const relY = newOffset[1] - initialOffset[1]; + const subTag = this.getTag(SubstituteTag); + if (duration) { + // TODO: can this use stricter typing? + const targets: any[] = [ this ]; + if (subTag?.sprite) { + targets.push(subTag.sprite); + } this.scene.tweens.add({ - targets: this, + targets: targets, x: (_target, _key, value: number) => value + relX, y: (_target, _key, value: number) => value + relY, duration: duration, @@ -761,6 +765,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } else { this.x += relX; this.y += relY; + if (subTag?.sprite) { + subTag.sprite.x += relX; + subTag.sprite.y += relY; + } } }); } @@ -906,39 +914,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let ret = statValue.value * this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated); switch (stat) { - case Stat.ATK: - if (this.getTag(BattlerTagType.SLOW_START)) { - ret >>= 1; - } - break; - case Stat.DEF: - if (this.isOfType(Type.ICE) && this.scene.arena.weather?.weatherType === WeatherType.SNOW) { - ret *= 1.5; - } - break; - case Stat.SPATK: - break; - case Stat.SPDEF: - if (this.isOfType(Type.ROCK) && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { - ret *= 1.5; - } - break; - case Stat.SPD: - const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (this.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) { - ret *= 2; - } - if (this.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) { - ret >>= 2; - } + case Stat.ATK: + if (this.getTag(BattlerTagType.SLOW_START)) { + ret >>= 1; + } + break; + case Stat.DEF: + if (this.isOfType(Type.ICE) && this.scene.arena.weather?.weatherType === WeatherType.SNOW) { + ret *= 1.5; + } + break; + case Stat.SPATK: + break; + case Stat.SPDEF: + if (this.isOfType(Type.ROCK) && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { + ret *= 1.5; + } + break; + case Stat.SPD: + const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + if (this.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) { + ret *= 2; + } + if (this.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) { + ret >>= 2; + } - if (this.getTag(BattlerTagType.SLOW_START)) { - ret >>= 1; - } - if (this.status && this.status.effect === StatusEffect.PARALYSIS) { - ret >>= 1; - } - break; + if (this.getTag(BattlerTagType.SLOW_START)) { + ret >>= 1; + } + if (this.status && this.status.effect === StatusEffect.PARALYSIS) { + ret >>= 1; + } + break; } const highestStatBoost = this.findTag(t => t instanceof HighestStatBoostTag && (t as HighestStatBoostTag).stat === stat) as HighestStatBoostTag; @@ -983,7 +991,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); } - statHolder.value = Utils.clampInt(statHolder.value, 1, Number.MAX_SAFE_INTEGER); + statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER); this.setStat(s, statHolder.value); } @@ -1012,7 +1020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getNature(): Nature { - return this.natureOverride !== -1 ? this.natureOverride : this.nature; + return this.customPokemonData.nature !== -1 ? this.customPokemonData.nature : this.nature; } setNature(nature: Nature): void { @@ -1187,15 +1195,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!types.length || !includeTeraType) { if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) { this.summonData.types.forEach(t => types.push(t)); - } else if (this.mysteryEncounterPokemonData.types && this.mysteryEncounterPokemonData.types.length > 0) { + } else if (this.customPokemonData.types && this.customPokemonData.types.length > 0) { // "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters - types.push(this.mysteryEncounterPokemonData.types[0]); + types.push(this.customPokemonData.types[0]); // Fusing a Pokemon onto something with "permanently changed" types will still apply the fusion's types as normal const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride); if (fusionSpeciesForm) { // Check if the fusion Pokemon also had "permanently changed" types - const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types; + const fusionMETypes = this.fusionCustomPokemonData?.types; if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) { types.push(fusionMETypes[1]); } else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) { @@ -1207,8 +1215,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - if (types.length === 1 && this.mysteryEncounterPokemonData.types.length >= 2) { - types.push(this.mysteryEncounterPokemonData.types[1]); + if (types.length === 1 && this.customPokemonData.types.length >= 2) { + types.push(this.customPokemonData.types[1]); } } else { const speciesForm = this.getSpeciesForm(ignoreOverride); @@ -1219,7 +1227,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (fusionSpeciesForm) { // Check if the fusion Pokemon also had "permanently changed" types // Otherwise, use standard fusion type logic - const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types; + const fusionMETypes = this.fusionCustomPokemonData?.types; if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) { types.push(fusionMETypes[1]); } else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) { @@ -1251,6 +1259,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } + // If both types are the same (can happen in weird custom typing scenarios), reduce to single type + if (types.length > 1 && types[0] === types[1]) { + types.splice(0, 1); + } + return types; } @@ -1277,14 +1290,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; } if (this.isFusion()) { - if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData.ability !== -1) { - return allAbilities[this.fusionMysteryEncounterPokemonData.ability]; + if (!isNullOrUndefined(this.fusionCustomPokemonData?.ability) && this.fusionCustomPokemonData.ability !== -1) { + return allAbilities[this.fusionCustomPokemonData.ability]; } else { return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; } } - if (!isNullOrUndefined(this.mysteryEncounterPokemonData.ability) && this.mysteryEncounterPokemonData.ability !== -1) { - return allAbilities[this.mysteryEncounterPokemonData.ability]; + if (!isNullOrUndefined(this.customPokemonData.ability) && this.customPokemonData.ability !== -1) { + return allAbilities[this.customPokemonData.ability]; } let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex); if (abilityId === Abilities.NONE) { @@ -1307,8 +1320,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; } - if (!isNullOrUndefined(this.mysteryEncounterPokemonData.passive) && this.mysteryEncounterPokemonData.passive !== -1) { - return allAbilities[this.mysteryEncounterPokemonData.passive]; + if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) { + return allAbilities[this.customPokemonData.passive]; } let starterSpeciesId = this.species.speciesId; @@ -1417,10 +1430,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns {boolean} Whether the ability is present and active */ hasAbility(ability: Abilities, canApply: boolean = true, ignoreOverride?: boolean): boolean { - if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).id === ability) { + if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) { return true; } - if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().id === ability) { + if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) { return true; } return false; @@ -1488,7 +1501,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } isGrounded(): boolean { - return !!this.getTag(GroundedTag) || (!this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.MAGNET_RISEN) && !this.getTag(SemiInvulnerableTag)); + return !!this.getTag(GroundedTag) || (!this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.FLOATING) && !this.getTag(SemiInvulnerableTag)); } /** @@ -1527,7 +1540,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder); - this.scene.arena.applyTags(ArenaTagType.ION_DELUGE, moveTypeHolder); + this.scene.arena.applyTags(ArenaTagType.ION_DELUGE, simulated, moveTypeHolder); if (this.getTag(BattlerTagType.ELECTRIFIED)) { moveTypeHolder.value = Type.ELECTRIC; } @@ -2007,7 +2020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionVariant = 0; this.fusionGender = 0; this.fusionLuck = 0; - this.fusionMysteryEncounterPokemonData = null; + this.fusionCustomPokemonData = null; this.generateName(); this.calculateStats(); @@ -2176,7 +2189,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.moveset.push(new PokemonMove(movePool[index][0], 0, 0)); } - this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); + // Trigger FormChange, except for enemy Pokemon during Mystery Encounters, to avoid crashes + if (this.isPlayer() || !this.scene.currentBattle?.isBattleMysteryEncounter() || !this.scene.currentBattle?.mysteryEncounter) { + this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); + } } trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean { @@ -2327,14 +2343,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (opponent) { if (isCritical) { switch (stat) { - case Stat.ATK: - case Stat.SPATK: - statStage.value = Math.max(statStage.value, 0); - break; - case Stat.DEF: - case Stat.SPDEF: - statStage.value = Math.min(statStage.value, 0); - break; + case Stat.ATK: + case Stat.SPATK: + statStage.value = Math.max(statStage.value, 0); + break; + case Stat.DEF: + case Stat.SPDEF: + statStage.value = Math.min(statStage.value, 0); + break; } } if (!ignoreOppAbility) { @@ -2594,7 +2610,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */ const screenMultiplier = new Utils.NumberHolder(1); - this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier); + this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, moveCategory, screenMultiplier); /** * For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if: @@ -2684,7 +2700,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ apply(source: Pokemon, move: Move): HitResult { const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (move.category === MoveCategory.STATUS) { + const moveCategory = new Utils.NumberHolder(move.category); + applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, moveCategory); + if (moveCategory.value === MoveCategory.STATUS) { const cancelled = new Utils.BooleanHolder(false); const typeMultiplier = this.getMoveEffectiveness(source, move, false, false, cancelled); @@ -2758,7 +2776,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (damage > 0) { if (source.isPlayer()) { - this.scene.validateAchvs(DamageAchv, damage); + this.scene.validateAchvs(DamageAchv, new Utils.NumberHolder(damage)); if (damage > this.scene.gameData.gameStats.highestDamage) { this.scene.gameData.gameStats.highestDamage = damage; } @@ -2782,30 +2800,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // want to include is.Fainted() in case multi hit move ends early, still want to render message if (source.turnData.hitsLeft === 1 || this.isFainted()) { switch (result) { - case HitResult.SUPER_EFFECTIVE: - this.scene.queueMessage(i18next.t("battle:hitResultSuperEffective")); - break; - case HitResult.NOT_VERY_EFFECTIVE: - this.scene.queueMessage(i18next.t("battle:hitResultNotVeryEffective")); - break; - case HitResult.ONE_HIT_KO: - this.scene.queueMessage(i18next.t("battle:hitResultOneHitKO")); - break; + case HitResult.SUPER_EFFECTIVE: + this.scene.queueMessage(i18next.t("battle:hitResultSuperEffective")); + break; + case HitResult.NOT_VERY_EFFECTIVE: + this.scene.queueMessage(i18next.t("battle:hitResultNotVeryEffective")); + break; + case HitResult.ONE_HIT_KO: + this.scene.queueMessage(i18next.t("battle:hitResultOneHitKO")); + break; } } if (this.isFainted()) { // set splice index here, so future scene queues happen before FaintedPhase this.scene.setPhaseQueueSplice(); - this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo)); + if (!isNullOrUndefined(destinyTag) && dmg) { + // Destiny Bond will activate during FaintPhase + this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo, destinyTag, source)); + } else { + this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo)); + } this.destroySubstitute(); this.resetSummonData(); } - if (dmg) { - destinyTag?.lapse(source, BattlerTagLapseType.CUSTOM); - } - return result; } } @@ -3035,6 +3054,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { continue; } + if (tag instanceof PowerTrickTag) { + tag.swapStat(this); + } + this.summonData.tags.push(tag); } @@ -3049,8 +3072,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * @see {@linkcode MoveRestrictionBattlerTag} */ - isMoveRestricted(moveId: Moves): boolean { - return this.getRestrictingTag(moveId) !== null; + public isMoveRestricted(moveId: Moves, pokemon?: Pokemon): boolean { + return this.getRestrictingTag(moveId, pokemon) !== null; } /** @@ -3083,7 +3106,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ getRestrictingTag(moveId: Moves, user?: Pokemon, target?: Pokemon): MoveRestrictionBattlerTag | null { for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) { - if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId)) { + if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId, user)) { return tag as MoveRestrictionBattlerTag; } else if (user && target && (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) { return tag as MoveRestrictionBattlerTag; @@ -3337,53 +3360,53 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } switch (effect) { - case StatusEffect.POISON: - case StatusEffect.TOXIC: + case StatusEffect.POISON: + case StatusEffect.TOXIC: // Check if the Pokemon is immune to Poison/Toxic or if the source pokemon is canceling the immunity - const poisonImmunity = types.map(defType => { + const poisonImmunity = types.map(defType => { // Check if the Pokemon is not immune to Poison/Toxic - if (defType !== Type.POISON && defType !== Type.STEEL) { - return false; - } + if (defType !== Type.POISON && defType !== Type.STEEL) { + return false; + } - // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity - const cancelImmunity = new Utils.BooleanHolder(false); - if (sourcePokemon) { - applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType); - if (cancelImmunity.value) { + // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity + const cancelImmunity = new Utils.BooleanHolder(false); + if (sourcePokemon) { + applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType); + if (cancelImmunity.value) { + return false; + } + } + + return true; + }); + + if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL)) { + if (poisonImmunity.includes(true)) { return false; } } - - return true; - }); - - if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL)) { - if (poisonImmunity.includes(true)) { + break; + case StatusEffect.PARALYSIS: + if (this.isOfType(Type.ELECTRIC)) { return false; } - } - break; - case StatusEffect.PARALYSIS: - if (this.isOfType(Type.ELECTRIC)) { - return false; - } - break; - case StatusEffect.SLEEP: - if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { - return false; - } - break; - case StatusEffect.FREEZE: - if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) { - return false; - } - break; - case StatusEffect.BURN: - if (this.isOfType(Type.FIRE)) { - return false; - } - break; + break; + case StatusEffect.SLEEP: + if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { + return false; + } + break; + case StatusEffect.FREEZE: + if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) { + return false; + } + break; + case StatusEffect.BURN: + if (this.isOfType(Type.FIRE)) { + return false; + } + break; } const cancelled = new Utils.BooleanHolder(false); @@ -3959,10 +3982,14 @@ export class PlayerPokemon extends Pokemon { if (Overrides.SHINY_OVERRIDE) { this.shiny = true; this.initShinySparkle(); - if (Overrides.VARIANT_OVERRIDE) { - this.variant = Overrides.VARIANT_OVERRIDE; - } + } else if (Overrides.SHINY_OVERRIDE === false) { + this.shiny = false; } + + if (Overrides.VARIANT_OVERRIDE !== null && this.shiny) { + this.variant = Overrides.VARIANT_OVERRIDE; + } + if (!dataSource) { if (this.scene.gameMode.isDaily) { this.generateAndPopulateMoveset(); @@ -4064,7 +4091,7 @@ export class PlayerPokemon extends Pokemon { fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null ].filter(d => !!d); const amount = new Utils.IntegerHolder(friendship); - const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? 2 : 1) / (fusionStarterSpeciesId ? 2 : 1))); + const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER : 1) / (fusionStarterSpeciesId ? 2 : 1))); if (amount.value > 0) { this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, starterAmount); @@ -4274,12 +4301,33 @@ export class PlayerPokemon extends Pokemon { changeForm(formChange: SpeciesFormChange): Promise { return new Promise(resolve => { + const previousFormIndex = this.formIndex; this.formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === formChange.formKey), 0); this.generateName(); const abilityCount = this.getSpeciesForm().getAbilityCount(); if (this.abilityIndex >= abilityCount) { // Shouldn't happen this.abilityIndex = abilityCount - 1; } + + // In cases where a form change updates the type of a Pokemon from its previous form (Arceus, Silvally, Castform, etc.), + // persist that type change in customPokemonData if necessary + const baseForm = this.species.forms[previousFormIndex]; + const baseFormTypes = [ baseForm.type1, baseForm.type2 ]; + if (this.customPokemonData.types.length > 0) { + if (this.getSpeciesForm().type1 !== baseFormTypes[0]) { + this.customPokemonData.types[0] = this.getSpeciesForm().type1; + } + + const type2 = this.getSpeciesForm().type2; + if (!isNullOrUndefined(type2) && type2 !== baseFormTypes[1]) { + if (this.customPokemonData.types.length > 1) { + this.customPokemonData.types[1] = type2; + } else { + this.customPokemonData.types.push(type2); + } + } + } + this.compatibleTms.splice(0, this.compatibleTms.length); this.generateCompatibleTms(); const updateAndResolve = () => { @@ -4316,7 +4364,7 @@ export class PlayerPokemon extends Pokemon { this.fusionVariant = pokemon.variant; this.fusionGender = pokemon.gender; this.fusionLuck = pokemon.luck; - this.fusionMysteryEncounterPokemonData = pokemon.mysteryEncounterPokemonData; + this.fusionCustomPokemonData = pokemon.customPokemonData; if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) { this.pauseEvolutions = true; } @@ -4430,10 +4478,13 @@ export class EnemyPokemon extends Pokemon { if (Overrides.OPP_SHINY_OVERRIDE) { this.shiny = true; this.initShinySparkle(); + } else if (Overrides.OPP_SHINY_OVERRIDE === false) { + this.shiny = false; } + if (this.shiny) { this.variant = this.generateVariant(); - if (Overrides.OPP_VARIANT_OVERRIDE) { + if (Overrides.OPP_VARIANT_OVERRIDE !== null) { this.variant = Overrides.OPP_VARIANT_OVERRIDE; } } @@ -4483,35 +4534,35 @@ export class EnemyPokemon extends Pokemon { generateAndPopulateMoveset(formIndex?: integer): void { switch (true) { - case (this.species.speciesId === Species.SMEARGLE): - this.moveset = [ - new PokemonMove(Moves.SKETCH), - new PokemonMove(Moves.SKETCH), - new PokemonMove(Moves.SKETCH), - new PokemonMove(Moves.SKETCH) - ]; - break; - case (this.species.speciesId === Species.ETERNATUS): - this.moveset = (formIndex !== undefined ? formIndex : this.formIndex) - ? [ - new PokemonMove(Moves.DYNAMAX_CANNON), - new PokemonMove(Moves.CROSS_POISON), - new PokemonMove(Moves.FLAMETHROWER), - new PokemonMove(Moves.RECOVER, 0, -4) - ] - : [ - new PokemonMove(Moves.ETERNABEAM), - new PokemonMove(Moves.SLUDGE_BOMB), - new PokemonMove(Moves.FLAMETHROWER), - new PokemonMove(Moves.COSMIC_POWER) + case (this.species.speciesId === Species.SMEARGLE): + this.moveset = [ + new PokemonMove(Moves.SKETCH), + new PokemonMove(Moves.SKETCH), + new PokemonMove(Moves.SKETCH), + new PokemonMove(Moves.SKETCH) ]; - if (this.scene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) { - this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT); - } - break; - default: - super.generateAndPopulateMoveset(); - break; + break; + case (this.species.speciesId === Species.ETERNATUS): + this.moveset = (formIndex !== undefined ? formIndex : this.formIndex) + ? [ + new PokemonMove(Moves.DYNAMAX_CANNON), + new PokemonMove(Moves.CROSS_POISON), + new PokemonMove(Moves.FLAMETHROWER), + new PokemonMove(Moves.RECOVER, 0, -4) + ] + : [ + new PokemonMove(Moves.ETERNABEAM), + new PokemonMove(Moves.SLUDGE_BOMB), + new PokemonMove(Moves.FLAMETHROWER), + new PokemonMove(Moves.COSMIC_POWER) + ]; + if (this.scene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) { + this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT); + } + break; + default: + super.generateAndPopulateMoveset(); + break; } } @@ -4551,135 +4602,135 @@ export class EnemyPokemon extends Pokemon { } } switch (this.aiType) { - case AiType.RANDOM: // No enemy should spawn with this AI type in-game - const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct? - return { move: moveId, targets: this.getNextTargets(moveId) }; - case AiType.SMART_RANDOM: - case AiType.SMART: + case AiType.RANDOM: // No enemy should spawn with this AI type in-game + const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct? + return { move: moveId, targets: this.getNextTargets(moveId) }; + case AiType.SMART_RANDOM: + case AiType.SMART: /** * Search this Pokemon's move pool for moves that will KO an opposing target. * If there are any moves that can KO an opponent (i.e. a player Pokemon), * those moves are the only ones considered for selection on this turn. */ - const koMoves = movePool.filter(pkmnMove => { - if (!pkmnMove) { - return false; - } + const koMoves = movePool.filter(pkmnMove => { + if (!pkmnMove) { + return false; + } - const move = pkmnMove.getMove()!; - if (move.moveTarget === MoveTarget.ATTACKER) { - return false; - } + const move = pkmnMove.getMove()!; + if (move.moveTarget === MoveTarget.ATTACKER) { + return false; + } - const fieldPokemon = this.scene.getField(); - const moveTargets = getMoveTargets(this, move.id).targets - .map(ind => fieldPokemon[ind]) - .filter(p => this.isPlayer() !== p.isPlayer()); - // Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus - const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); + const fieldPokemon = this.scene.getField(); + const moveTargets = getMoveTargets(this, move.id).targets + .map(ind => fieldPokemon[ind]) + .filter(p => this.isPlayer() !== p.isPlayer()); + // Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus + const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); - return move.category !== MoveCategory.STATUS + return move.category !== MoveCategory.STATUS && moveTargets.some(p => { const doesNotFail = move.applyConditions(this, p, move) || [ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id); return doesNotFail && p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, isCritical).damage >= p.hp; }); - }, this); + }, this); - if (koMoves.length > 0) { - movePool = koMoves; - } + if (koMoves.length > 0) { + movePool = koMoves; + } - /** + /** * Move selection is based on the move's calculated "benefit score" against the * best possible target(s) (as determined by {@linkcode getNextTargets}). * For more information on how benefit scores are calculated, see `docs/enemy-ai.md`. */ - const moveScores = movePool.map(() => 0); - const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct? - for (const m in movePool) { - const pokemonMove = movePool[m]!; // TODO: is the bang correct? - const move = pokemonMove.getMove(); + const moveScores = movePool.map(() => 0); + const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct? + for (const m in movePool) { + const pokemonMove = movePool[m]!; // TODO: is the bang correct? + const move = pokemonMove.getMove(); - let moveScore = moveScores[m]; - const targetScores: integer[] = []; + let moveScore = moveScores[m]; + const targetScores: integer[] = []; - for (const mt of moveTargets[move.id]) { + for (const mt of moveTargets[move.id]) { // Prevent a target score from being calculated when the target is whoever attacks the user - if (mt === BattlerIndex.ATTACKER) { - break; - } + if (mt === BattlerIndex.ATTACKER) { + break; + } - const target = this.scene.getField()[mt]; - /** + const target = this.scene.getField()[mt]; + /** * The "target score" of a move is given by the move's user benefit score + the move's target benefit score. * If the target is an ally, the target benefit score is multiplied by -1. */ - let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); - if (Number.isNaN(targetScore)) { - console.error(`Move ${move.name} returned score of NaN`); - targetScore = 0; - } - /** + let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); + if (Number.isNaN(targetScore)) { + console.error(`Move ${move.name} returned score of NaN`); + targetScore = 0; + } + /** * If this move is unimplemented, or the move is known to fail when used, set its * target score to -20 */ - if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id)) { - targetScore = -20; - } else if (move instanceof AttackMove) { + if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id)) { + targetScore = -20; + } else if (move instanceof AttackMove) { /** * Attack moves are given extra multipliers to their base benefit score based on * the move's type effectiveness against the target and whether the move is a STAB move. */ - const effectiveness = target.getMoveEffectiveness(this, move, !target.battleData?.abilityRevealed); - if (target.isPlayer() !== this.isPlayer()) { - targetScore *= effectiveness; - if (this.isOfType(move.type)) { - targetScore *= 1.5; + const effectiveness = target.getMoveEffectiveness(this, move, !target.battleData?.abilityRevealed); + if (target.isPlayer() !== this.isPlayer()) { + targetScore *= effectiveness; + if (this.isOfType(move.type)) { + targetScore *= 1.5; + } + } else if (effectiveness) { + targetScore /= effectiveness; + if (this.isOfType(move.type)) { + targetScore /= 1.5; + } } - } else if (effectiveness) { - targetScore /= effectiveness; - if (this.isOfType(move.type)) { - targetScore /= 1.5; + /** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */ + if (!targetScore) { + targetScore = -20; } } - /** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */ - if (!targetScore) { - targetScore = -20; - } + targetScores.push(targetScore); } - targetScores.push(targetScore); + // When a move has multiple targets, its score is equal to the maximum target score across all targets + moveScore += Math.max(...targetScores); + + // could make smarter by checking opponent def/spdef + moveScores[m] = moveScore; } - // When a move has multiple targets, its score is equal to the maximum target score across all targets - moveScore += Math.max(...targetScores); - // could make smarter by checking opponent def/spdef - moveScores[m] = moveScore; - } + console.log(moveScores); - console.log(moveScores); - - // Sort the move pool in decreasing order of move score - const sortedMovePool = movePool.slice(0); - sortedMovePool.sort((a, b) => { - const scoreA = moveScores[movePool.indexOf(a)]; - const scoreB = moveScores[movePool.indexOf(b)]; - return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0; - }); - let r = 0; - if (this.aiType === AiType.SMART_RANDOM) { + // Sort the move pool in decreasing order of move score + const sortedMovePool = movePool.slice(0); + sortedMovePool.sort((a, b) => { + const scoreA = moveScores[movePool.indexOf(a)]; + const scoreB = moveScores[movePool.indexOf(b)]; + return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0; + }); + let r = 0; + if (this.aiType === AiType.SMART_RANDOM) { // Has a 5/8 chance to select the best move, and a 3/8 chance to advance to the next best move (and repeat this roll) - while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) { - r++; - } - } else if (this.aiType === AiType.SMART) { + while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) { + r++; + } + } else if (this.aiType === AiType.SMART) { // The chance to advance to the next best move increases when the compared moves' scores are closer to each other. - while (r < sortedMovePool.length - 1 && (moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) >= 0 + while (r < sortedMovePool.length - 1 && (moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) >= 0 && this.scene.randBattleSeedInt(100) < Math.round((moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) * 50)) { - r++; + r++; + } } - } - console.log(movePool.map(m => m!.getName()), moveScores, r, sortedMovePool.map(m => m!.getName())); // TODO: are those bangs correct? - return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] }; + console.log(movePool.map(m => m!.getName()), moveScores, r, sortedMovePool.map(m => m!.getName())); // TODO: are those bangs correct? + return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] }; } } @@ -4827,10 +4878,10 @@ export class EnemyPokemon extends Pokemon { } switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - if (!this.formIndex && this.bossSegmentIndex < 1) { - damage = Math.min(damage, this.hp - 1); - } + case BattleSpec.FINAL_BOSS: + if (!this.formIndex && this.bossSegmentIndex < 1) { + damage = Math.min(damage, this.hp - 1); + } } const ret = super.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase); @@ -5126,7 +5177,7 @@ export class PokemonMove { * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. */ isUsable(pokemon: Pokemon, ignorePp: boolean = false, ignoreRestrictionTags: boolean = false): boolean { - if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId)) { + if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) { return false; } diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 2ec9d07e474..b77a156f401 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -65,16 +65,16 @@ export default class Trainer extends Phaser.GameObjects.Container { } switch (this.variant) { - case TrainerVariant.FEMALE: - if (!this.config.hasGenders) { - variant = TrainerVariant.DEFAULT; - } - break; - case TrainerVariant.DOUBLE: - if (!this.config.hasDouble) { - variant = TrainerVariant.DEFAULT; - } - break; + case TrainerVariant.FEMALE: + if (!this.config.hasGenders) { + variant = TrainerVariant.DEFAULT; + } + break; + case TrainerVariant.DOUBLE: + if (!this.config.hasDouble) { + variant = TrainerVariant.DEFAULT; + } + break; } console.log(Object.keys(trainerPartyTemplates)[Object.values(trainerPartyTemplates).indexOf(this.getPartyTemplate())]); @@ -229,21 +229,21 @@ export default class Trainer extends Phaser.GameObjects.Container { const strength = partyTemplate.getStrength(i); switch (strength) { - case PartyMemberStrength.WEAKER: - multiplier = 0.95; - break; - case PartyMemberStrength.WEAK: - multiplier = 1.0; - break; - case PartyMemberStrength.AVERAGE: - multiplier = 1.1; - break; - case PartyMemberStrength.STRONG: - multiplier = 1.2; - break; - case PartyMemberStrength.STRONGER: - multiplier = 1.25; - break; + case PartyMemberStrength.WEAKER: + multiplier = 0.95; + break; + case PartyMemberStrength.WEAK: + multiplier = 1.0; + break; + case PartyMemberStrength.AVERAGE: + multiplier = 1.1; + break; + case PartyMemberStrength.STRONG: + multiplier = 1.2; + break; + case PartyMemberStrength.STRONGER: + multiplier = 1.25; + break; } let levelOffset = 0; @@ -383,7 +383,7 @@ export default class Trainer extends Phaser.GameObjects.Container { const battle = this.scene.currentBattle; const template = this.getPartyTemplate(); - let species: PokemonSpecies; + let baseSpecies: PokemonSpecies; if (this.config.speciesPools) { const tierValue = Utils.randSeedInt(512); let tier = tierValue >= 156 ? TrainerPoolTier.COMMON : tierValue >= 32 ? TrainerPoolTier.UNCOMMON : tierValue >= 6 ? TrainerPoolTier.RARE : tierValue >= 1 ? TrainerPoolTier.SUPER_RARE : TrainerPoolTier.ULTRA_RARE; @@ -393,17 +393,17 @@ export default class Trainer extends Phaser.GameObjects.Container { tier--; } const tierPool = this.config.speciesPools[tier]; - species = getPokemonSpecies(Utils.randSeedItem(tierPool)); + baseSpecies = getPokemonSpecies(Utils.randSeedItem(tierPool)); } else { - species = this.scene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter); + baseSpecies = this.scene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter); } - let ret = getPokemonSpecies(species.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex)); + let ret = getPokemonSpecies(baseSpecies.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex)); let retry = false; console.log(ret.getName()); - if (pokemonPrevolutions.hasOwnProperty(species.speciesId) && ret.speciesId !== species.speciesId) { + if (pokemonPrevolutions.hasOwnProperty(baseSpecies.speciesId) && ret.speciesId !== baseSpecies.speciesId) { retry = true; } else if (template.isBalanced(battle.enemyParty.length)) { const partyMemberTypes = battle.enemyParty.map(p => p.getTypes(true)).flat(); @@ -417,7 +417,7 @@ export default class Trainer extends Phaser.GameObjects.Container { console.log("Attempting reroll of species evolution to fit specialty type..."); let evoAttempt = 0; while (retry && evoAttempt++ < 10) { - ret = getPokemonSpecies(species.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex)); + ret = getPokemonSpecies(baseSpecies.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex)); console.log(ret.name); if (this.config.specialtyTypes.find(t => ret.isOfType(t))) { retry = false; @@ -426,7 +426,7 @@ export default class Trainer extends Phaser.GameObjects.Container { } // Prompts reroll of party member species if species already present in the enemy party - if (this.checkDuplicateSpecies(ret)) { + if (this.checkDuplicateSpecies(ret, baseSpecies)) { console.log("Duplicate species detected, prompting reroll..."); retry = true; } @@ -442,13 +442,16 @@ export default class Trainer extends Phaser.GameObjects.Container { /** * Checks if the enemy trainer already has the Pokemon species in their party * @param {PokemonSpecies} species {@linkcode PokemonSpecies} + * @param {PokemonSpecies} baseSpecies {@linkcode PokemonSpecies} - baseSpecies of the Pokemon if species is forced to evolve * @returns `true` if the species is already present in the party */ - checkDuplicateSpecies(species: PokemonSpecies): boolean { + checkDuplicateSpecies(species: PokemonSpecies, baseSpecies: PokemonSpecies): boolean { + const staticPartyPokemon = (signatureSpecies[TrainerType[this.config.trainerType]] ?? []).flat(1); + const currentPartySpecies = this.scene.getEnemyParty().map(p => { return p.species.speciesId; }); - return currentPartySpecies.includes(species.speciesId); + return currentPartySpecies.includes(species.speciesId) || staticPartyPokemon.includes(baseSpecies.speciesId); } getPartyMemberMatchupScores(trainerSlot: TrainerSlot = TrainerSlot.NONE, forSwitch: boolean = false): [integer, integer][] { @@ -512,19 +515,19 @@ export default class Trainer extends Phaser.GameObjects.Container { getPartyMemberModifierChanceMultiplier(index: integer): number { switch (this.getPartyTemplate().getStrength(index)) { - case PartyMemberStrength.WEAKER: - return 0.75; - case PartyMemberStrength.WEAK: - return 0.675; - case PartyMemberStrength.AVERAGE: - return 0.5625; - case PartyMemberStrength.STRONG: - return 0.45; - case PartyMemberStrength.STRONGER: - return 0.375; - default: - console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0"); - return 0; + case PartyMemberStrength.WEAKER: + return 0.75; + case PartyMemberStrength.WEAK: + return 0.675; + case PartyMemberStrength.AVERAGE: + return 0.5625; + case PartyMemberStrength.STRONG: + return 0.45; + case PartyMemberStrength.STRONGER: + return 0.375; + default: + console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0"); + return 0; } } diff --git a/src/game-mode.ts b/src/game-mode.ts index 91e8933ea6e..8f1bb9356e6 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -92,10 +92,10 @@ export class GameMode implements GameModeConfig { return Overrides.STARTING_LEVEL_OVERRIDE; } switch (this.modeId) { - case GameModes.DAILY: - return 20; - default: - return 5; + case GameModes.DAILY: + return 20; + default: + return 5; } } @@ -117,19 +117,19 @@ export class GameMode implements GameModeConfig { */ getStartingBiome(scene: BattleScene): Biome { switch (this.modeId) { - case GameModes.DAILY: - return scene.generateRandomBiome(this.getWaveForDifficulty(1)); - default: - return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; + case GameModes.DAILY: + return scene.generateRandomBiome(this.getWaveForDifficulty(1)); + default: + return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; } } getWaveForDifficulty(waveIndex: integer, ignoreCurveChanges: boolean = false): integer { switch (this.modeId) { - case GameModes.DAILY: - return waveIndex + 30 + (!ignoreCurveChanges ? Math.floor(waveIndex / 5) : 0); - default: - return waveIndex; + case GameModes.DAILY: + return waveIndex + 30 + (!ignoreCurveChanges ? Math.floor(waveIndex / 5) : 0); + default: + return waveIndex; } } @@ -186,10 +186,10 @@ export class GameMode implements GameModeConfig { isTrainerBoss(waveIndex: integer, biomeType: Biome, offsetGym: boolean): boolean { switch (this.modeId) { - case GameModes.DAILY: - return waveIndex > 10 && waveIndex < 50 && !(waveIndex % 10); - default: - return (waveIndex % 30) === (offsetGym ? 0 : 20) && (biomeType !== Biome.END || this.isClassic || this.isWaveFinal(waveIndex)); + case GameModes.DAILY: + return waveIndex > 10 && waveIndex < 50 && !(waveIndex % 10); + default: + return (waveIndex % 30) === (offsetGym ? 0 : 20) && (biomeType !== Biome.END || this.isClassic || this.isWaveFinal(waveIndex)); } } @@ -211,14 +211,14 @@ export class GameMode implements GameModeConfig { */ isWaveFinal(waveIndex: integer, modeId: GameModes = this.modeId): boolean { switch (modeId) { - case GameModes.CLASSIC: - case GameModes.CHALLENGE: - return waveIndex === 200; - case GameModes.ENDLESS: - case GameModes.SPLICED_ENDLESS: - return !(waveIndex % 250); - case GameModes.DAILY: - return waveIndex === 50; + case GameModes.CLASSIC: + case GameModes.CHALLENGE: + return waveIndex === 200; + case GameModes.ENDLESS: + case GameModes.SPLICED_ENDLESS: + return !(waveIndex % 250); + case GameModes.DAILY: + return waveIndex === 50; } } @@ -287,40 +287,40 @@ export class GameMode implements GameModeConfig { getClearScoreBonus(): integer { switch (this.modeId) { - case GameModes.CLASSIC: - case GameModes.CHALLENGE: - return 5000; - case GameModes.DAILY: - return 2500; - default: - return 0; + case GameModes.CLASSIC: + case GameModes.CHALLENGE: + return 5000; + case GameModes.DAILY: + return 2500; + default: + return 0; } } getEnemyModifierChance(isBoss: boolean): integer { switch (this.modeId) { - case GameModes.CLASSIC: - case GameModes.CHALLENGE: - case GameModes.DAILY: - return !isBoss ? 18 : 6; - case GameModes.ENDLESS: - case GameModes.SPLICED_ENDLESS: - return !isBoss ? 12 : 4; + case GameModes.CLASSIC: + case GameModes.CHALLENGE: + case GameModes.DAILY: + return !isBoss ? 18 : 6; + case GameModes.ENDLESS: + case GameModes.SPLICED_ENDLESS: + return !isBoss ? 12 : 4; } } getName(): string { switch (this.modeId) { - case GameModes.CLASSIC: - return i18next.t("gameMode:classic"); - case GameModes.ENDLESS: - return i18next.t("gameMode:endless"); - case GameModes.SPLICED_ENDLESS: - return i18next.t("gameMode:endlessSpliced"); - case GameModes.DAILY: - return i18next.t("gameMode:dailyRun"); - case GameModes.CHALLENGE: - return i18next.t("gameMode:challenge"); + case GameModes.CLASSIC: + return i18next.t("gameMode:classic"); + case GameModes.ENDLESS: + return i18next.t("gameMode:endless"); + case GameModes.SPLICED_ENDLESS: + return i18next.t("gameMode:endlessSpliced"); + case GameModes.DAILY: + return i18next.t("gameMode:dailyRun"); + case GameModes.CHALLENGE: + return i18next.t("gameMode:challenge"); } } @@ -329,42 +329,42 @@ export class GameMode implements GameModeConfig { */ getMysteryEncounterLegalWaves(): [number, number] { switch (this.modeId) { - default: - return [ 0, 0 ]; - case GameModes.CLASSIC: - return CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES; - case GameModes.CHALLENGE: - return CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES; + default: + return [ 0, 0 ]; + case GameModes.CLASSIC: + return CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES; + case GameModes.CHALLENGE: + return CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES; } } static getModeName(modeId: GameModes): string { switch (modeId) { - case GameModes.CLASSIC: - return i18next.t("gameMode:classic"); - case GameModes.ENDLESS: - return i18next.t("gameMode:endless"); - case GameModes.SPLICED_ENDLESS: - return i18next.t("gameMode:endlessSpliced"); - case GameModes.DAILY: - return i18next.t("gameMode:dailyRun"); - case GameModes.CHALLENGE: - return i18next.t("gameMode:challenge"); + case GameModes.CLASSIC: + return i18next.t("gameMode:classic"); + case GameModes.ENDLESS: + return i18next.t("gameMode:endless"); + case GameModes.SPLICED_ENDLESS: + return i18next.t("gameMode:endlessSpliced"); + case GameModes.DAILY: + return i18next.t("gameMode:dailyRun"); + case GameModes.CHALLENGE: + return i18next.t("gameMode:challenge"); } } } export function getGameMode(gameMode: GameModes): GameMode { switch (gameMode) { - case GameModes.CLASSIC: - return new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true, hasMysteryEncounters: true }, classicFixedBattles); - case GameModes.ENDLESS: - return new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true }); - case GameModes.SPLICED_ENDLESS: - return new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true }); - case GameModes.DAILY: - return new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true }); - case GameModes.CHALLENGE: - return new GameMode(GameModes.CHALLENGE, { isClassic: true, hasTrainers: true, isChallenge: true, hasMysteryEncounters: true }, classicFixedBattles); + case GameModes.CLASSIC: + return new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true, hasMysteryEncounters: true }, classicFixedBattles); + case GameModes.ENDLESS: + return new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true }); + case GameModes.SPLICED_ENDLESS: + return new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true }); + case GameModes.DAILY: + return new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true }); + case GameModes.CHALLENGE: + return new GameMode(GameModes.CHALLENGE, { isClassic: true, hasTrainers: true, isChallenge: true, hasMysteryEncounters: true }, classicFixedBattles); } } diff --git a/src/loading-scene.ts b/src/loading-scene.ts index d6512486d0e..4f673fd2cfc 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -477,18 +477,18 @@ export class LoadingScene extends SceneBase { this.load.on(this.LOAD_EVENTS.FILE_COMPLETE, (key: string) => { assetText.setText(i18next.t("menu:loadingAsset", { assetName: key })); switch (key) { - case "loading_bg": - bg.setTexture("loading_bg"); - if (mobile) { - bg.setVisible(true); - } - break; - case "logo": - logo.setTexture("logo"); - if (mobile) { - logo.setVisible(true); - } - break; + case "loading_bg": + bg.setTexture("loading_bg"); + if (mobile) { + bg.setVisible(true); + } + break; + case "logo": + logo.setTexture("logo"); + if (mobile) { + logo.setVisible(true); + } + break; } }); diff --git a/src/messages.ts b/src/messages.ts index 1cd6a5966b6..91f550918e5 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -13,21 +13,21 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined): string { } switch (pokemon.scene.currentBattle.battleSpec) { - case BattleSpec.DEFAULT: - return !pokemon.isPlayer() - ? pokemon.hasTrainer() - ? i18next.t("battle:foePokemonWithAffix", { - pokemonName: pokemon.getNameToRender(), - }) - : i18next.t("battle:wildPokemonWithAffix", { - pokemonName: pokemon.getNameToRender(), - }) - : pokemon.getNameToRender(); - case BattleSpec.FINAL_BOSS: - return !pokemon.isPlayer() - ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender() }) - : pokemon.getNameToRender(); - default: - return pokemon.getNameToRender(); + case BattleSpec.DEFAULT: + return !pokemon.isPlayer() + ? pokemon.hasTrainer() + ? i18next.t("battle:foePokemonWithAffix", { + pokemonName: pokemon.getNameToRender(), + }) + : i18next.t("battle:wildPokemonWithAffix", { + pokemonName: pokemon.getNameToRender(), + }) + : pokemon.getNameToRender(); + case BattleSpec.FINAL_BOSS: + return !pokemon.isPlayer() + ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender() }) + : pokemon.getNameToRender(); + default: + return pokemon.getNameToRender(); } } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index f20aa854bdf..8e7853a41bb 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -78,18 +78,18 @@ export class ModifierType { } let poolTypes: ModifierPoolType[]; switch (poolType) { - case ModifierPoolType.PLAYER: - poolTypes = [ poolType, ModifierPoolType.TRAINER, ModifierPoolType.WILD ]; - break; - case ModifierPoolType.WILD: - poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.TRAINER ]; - break; - case ModifierPoolType.TRAINER: - poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.WILD ]; - break; - default: - poolTypes = [ poolType ]; - break; + case ModifierPoolType.PLAYER: + poolTypes = [ poolType, ModifierPoolType.TRAINER, ModifierPoolType.WILD ]; + break; + case ModifierPoolType.WILD: + poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.TRAINER ]; + break; + case ModifierPoolType.TRAINER: + poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.WILD ]; + break; + default: + poolTypes = [ poolType ]; + break; } // Try multiple pool types in case of stolen items for (const type of poolTypes) { @@ -502,42 +502,42 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge function getAttackTypeBoosterItemName(type: Type) { switch (type) { - case Type.NORMAL: - return "Silk Scarf"; - case Type.FIGHTING: - return "Black Belt"; - case Type.FLYING: - return "Sharp Beak"; - case Type.POISON: - return "Poison Barb"; - case Type.GROUND: - return "Soft Sand"; - case Type.ROCK: - return "Hard Stone"; - case Type.BUG: - return "Silver Powder"; - case Type.GHOST: - return "Spell Tag"; - case Type.STEEL: - return "Metal Coat"; - case Type.FIRE: - return "Charcoal"; - case Type.WATER: - return "Mystic Water"; - case Type.GRASS: - return "Miracle Seed"; - case Type.ELECTRIC: - return "Magnet"; - case Type.PSYCHIC: - return "Twisted Spoon"; - case Type.ICE: - return "Never-Melt Ice"; - case Type.DRAGON: - return "Dragon Fang"; - case Type.DARK: - return "Black Glasses"; - case Type.FAIRY: - return "Fairy Feather"; + case Type.NORMAL: + return "Silk Scarf"; + case Type.FIGHTING: + return "Black Belt"; + case Type.FLYING: + return "Sharp Beak"; + case Type.POISON: + return "Poison Barb"; + case Type.GROUND: + return "Soft Sand"; + case Type.ROCK: + return "Hard Stone"; + case Type.BUG: + return "Silver Powder"; + case Type.GHOST: + return "Spell Tag"; + case Type.STEEL: + return "Metal Coat"; + case Type.FIRE: + return "Charcoal"; + case Type.WATER: + return "Mystic Water"; + case Type.GRASS: + return "Miracle Seed"; + case Type.ELECTRIC: + return "Magnet"; + case Type.PSYCHIC: + return "Twisted Spoon"; + case Type.ICE: + return "Never-Melt Ice"; + case Type.DRAGON: + return "Dragon Fang"; + case Type.DARK: + return "Black Glasses"; + case Type.FAIRY: + return "Fairy Feather"; } } @@ -1126,7 +1126,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { } class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { - constructor(rare: boolean) { + constructor(isRareFormChangeItem: boolean) { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in FormChangeItem)) { return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem); @@ -1149,15 +1149,15 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { foundN_SOLAR = false; formChangeItemTriggers.forEach((fc, i) => { switch (fc.item) { - case FormChangeItem.ULTRANECROZIUM_Z: - foundULTRA_Z = true; - break; - case FormChangeItem.N_LUNARIZER: - foundN_LUNA = true; - break; - case FormChangeItem.N_SOLARIZER: - foundN_SOLAR = true; - break; + case FormChangeItem.ULTRANECROZIUM_Z: + foundULTRA_Z = true; + break; + case FormChangeItem.N_LUNARIZER: + foundN_LUNA = true; + break; + case FormChangeItem.N_SOLARIZER: + foundN_SOLAR = true; + break; } }); if (foundULTRA_Z && foundN_LUNA && foundN_SOLAR) { @@ -1167,7 +1167,7 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { } return formChangeItemTriggers; }).flat()) - ].flat().flatMap(fc => fc.item).filter(i => (i && i < 100) === rare); + ].flat().flatMap(fc => fc.item).filter(i => (i && i < 100) === isRareFormChangeItem); // convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party. if (!formChangeItemPool.length) { @@ -1975,21 +1975,21 @@ let enemyBuffIgnoredPoolIndexes = {}; // eslint-disable-line @typescript-eslint/ export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool { let pool: ModifierPool; switch (poolType) { - case ModifierPoolType.PLAYER: - pool = modifierPool; - break; - case ModifierPoolType.WILD: - pool = wildModifierPool; - break; - case ModifierPoolType.TRAINER: - pool = trainerModifierPool; - break; - case ModifierPoolType.ENEMY_BUFF: - pool = enemyBuffModifierPool; - break; - case ModifierPoolType.DAILY_STARTER: - pool = dailyStarterModifierPool; - break; + case ModifierPoolType.PLAYER: + pool = modifierPool; + break; + case ModifierPoolType.WILD: + pool = wildModifierPool; + break; + case ModifierPoolType.TRAINER: + pool = trainerModifierPool; + break; + case ModifierPoolType.ENEMY_BUFF: + pool = enemyBuffModifierPool; + break; + case ModifierPoolType.DAILY_STARTER: + pool = dailyStarterModifierPool; + break; } return pool; } @@ -2060,23 +2060,23 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod console.table(modifierTableData); } switch (poolType) { - case ModifierPoolType.PLAYER: - modifierPoolThresholds = thresholds; - ignoredPoolIndexes = ignoredIndexes; - break; - case ModifierPoolType.WILD: - case ModifierPoolType.TRAINER: - enemyModifierPoolThresholds = thresholds; - enemyIgnoredPoolIndexes = ignoredIndexes; - break; - case ModifierPoolType.ENEMY_BUFF: - enemyBuffModifierPoolThresholds = thresholds; - enemyBuffIgnoredPoolIndexes = ignoredIndexes; - break; - case ModifierPoolType.DAILY_STARTER: - dailyStarterModifierPoolThresholds = thresholds; - ignoredDailyStarterPoolIndexes = ignoredIndexes; - break; + case ModifierPoolType.PLAYER: + modifierPoolThresholds = thresholds; + ignoredPoolIndexes = ignoredIndexes; + break; + case ModifierPoolType.WILD: + case ModifierPoolType.TRAINER: + enemyModifierPoolThresholds = thresholds; + enemyIgnoredPoolIndexes = ignoredIndexes; + break; + case ModifierPoolType.ENEMY_BUFF: + enemyBuffModifierPoolThresholds = thresholds; + enemyBuffIgnoredPoolIndexes = ignoredIndexes; + break; + case ModifierPoolType.DAILY_STARTER: + dailyStarterModifierPoolThresholds = thresholds; + ignoredDailyStarterPoolIndexes = ignoredIndexes; + break; } } @@ -2227,7 +2227,8 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base ], [ new ModifierTypeOption(modifierTypes.HYPER_POTION(), 0, baseCost * 0.8), - new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75) + new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75), + new ModifierTypeOption(modifierTypes.MEMORY_MUSHROOM(), 0, baseCost * 4) ], [ new ModifierTypeOption(modifierTypes.MAX_POTION(), 0, baseCost * 1.5), @@ -2246,15 +2247,15 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: PersistentModifier[], scene: BattleScene): EnemyPersistentModifier { let tierStackCount: number; switch (tier) { - case ModifierTier.ULTRA: - tierStackCount = 5; - break; - case ModifierTier.GREAT: - tierStackCount = 3; - break; - default: - tierStackCount = 1; - break; + case ModifierTier.ULTRA: + tierStackCount = 5; + break; + case ModifierTier.GREAT: + tierStackCount = 3; + break; + default: + tierStackCount = 1; + break; } const retryCount = 50; @@ -2320,21 +2321,21 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, const pool = getModifierPoolForType(poolType); let thresholds: object; switch (poolType) { - case ModifierPoolType.PLAYER: - thresholds = modifierPoolThresholds; - break; - case ModifierPoolType.WILD: - thresholds = enemyModifierPoolThresholds; - break; - case ModifierPoolType.TRAINER: - thresholds = enemyModifierPoolThresholds; - break; - case ModifierPoolType.ENEMY_BUFF: - thresholds = enemyBuffModifierPoolThresholds; - break; - case ModifierPoolType.DAILY_STARTER: - thresholds = dailyStarterModifierPoolThresholds; - break; + case ModifierPoolType.PLAYER: + thresholds = modifierPoolThresholds; + break; + case ModifierPoolType.WILD: + thresholds = enemyModifierPoolThresholds; + break; + case ModifierPoolType.TRAINER: + thresholds = enemyModifierPoolThresholds; + break; + case ModifierPoolType.ENEMY_BUFF: + thresholds = enemyBuffModifierPoolThresholds; + break; + case ModifierPoolType.DAILY_STARTER: + thresholds = dailyStarterModifierPoolThresholds; + break; } if (tier === undefined) { const tierValue = randSeedInt(1024); diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index b658d3b5277..b699c2483c9 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -11,7 +11,7 @@ import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { EvolutionPhase } from "#app/phases/evolution-phase"; -import { LearnMovePhase } from "#app/phases/learn-move-phase"; +import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase"; import { LevelUpPhase } from "#app/phases/level-up-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { achvs } from "#app/system/achv"; @@ -30,6 +30,7 @@ import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type"; import { Color, ShadowColor } from "#enums/color"; +import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -1694,12 +1695,12 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier { super(type, pokemonId, stackCount); switch (type.id) { - case "TOXIC_ORB": - this.effect = StatusEffect.TOXIC; - break; - case "FLAME_ORB": - this.effect = StatusEffect.BURN; - break; + case "TOXIC_ORB": + this.effect = StatusEffect.TOXIC; + break; + case "FLAME_ORB": + this.effect = StatusEffect.BURN; + break; } } @@ -2180,7 +2181,7 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier { * @returns */ override apply(playerPokemon: PlayerPokemon): boolean { - playerPokemon.natureOverride = this.nature; + playerPokemon.customPokemonData.nature = this.nature; let speciesId = playerPokemon.species.speciesId; playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); @@ -2213,7 +2214,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier { playerPokemon.levelExp = 0; } - playerPokemon.addFriendship(5); + playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY); playerPokemon.scene.unshiftPhase(new LevelUpPhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.level - levelCount.value, playerPokemon.level)); @@ -2235,7 +2236,7 @@ export class TmModifier extends ConsumablePokemonModifier { */ override apply(playerPokemon: PlayerPokemon): boolean { - playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), this.type.moveId, true)); + playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), this.type.moveId, LearnMoveType.TM)); return true; } @@ -2255,8 +2256,9 @@ export class RememberMoveModifier extends ConsumablePokemonModifier { * @param playerPokemon The {@linkcode PlayerPokemon} that should remember the move * @returns always `true` */ - override apply(playerPokemon: PlayerPokemon): boolean { - playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex])); + override apply(playerPokemon: PlayerPokemon, cost?: number): boolean { + + playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], LearnMoveType.MEMORY, cost)); return true; } @@ -2682,15 +2684,15 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier { count.value *= (this.getStackCount() + 1); switch (this.getStackCount()) { - case 1: - power.value *= 0.4; - break; - case 2: - power.value *= 0.25; - break; - case 3: - power.value *= 0.175; - break; + case 1: + power.value *= 0.4; + break; + case 2: + power.value *= 0.25; + break; + case 3: + power.value *= 0.175; + break; } return true; @@ -3084,11 +3086,12 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { * Steals an item from a set of target Pokemon. * This prioritizes high-tier held items when selecting the item to steal. * @param pokemon The {@linkcode Pokemon} holding this item + * @param target The {@linkcode Pokemon} to steal from (optional) * @param _args N/A * @returns `true` if an item was stolen; false otherwise. */ - override apply(pokemon: Pokemon, ..._args: unknown[]): boolean { - const opponents = this.getTargets(pokemon); + override apply(pokemon: Pokemon, target?: Pokemon, ..._args: unknown[]): boolean { + const opponents = this.getTargets(pokemon, target); if (!opponents.length) { return false; @@ -3187,7 +3190,7 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { * @see {@linkcode HeldItemTransferModifier} */ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModifier { - private chance: number; + public readonly chance: number; constructor(type: ModifierType, pokemonId: number, chancePercent: number, stackCount?: number) { super(type, pokemonId, stackCount); @@ -3634,7 +3637,7 @@ export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, isPlayer } if (!isPlayer) { - scene.clearEnemyHeldItemModifiers(); + scene.clearEnemyHeldItemModifiers(pokemon); } heldItemsOverride.forEach(item => { diff --git a/src/overrides.ts b/src/overrides.ts index 27886ded2f2..e1bfbd240f0 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -113,8 +113,8 @@ class DefaultOverrides { readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly GENDER_OVERRIDE: Gender | null = null; readonly MOVESET_OVERRIDE: Moves | Array = []; - readonly SHINY_OVERRIDE: boolean = false; - readonly VARIANT_OVERRIDE: Variant = 0; + readonly SHINY_OVERRIDE: boolean | null = null; + readonly VARIANT_OVERRIDE: Variant | null = null; // -------------------------- // OPPONENT / ENEMY OVERRIDES @@ -134,8 +134,8 @@ class DefaultOverrides { readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly OPP_GENDER_OVERRIDE: Gender | null = null; readonly OPP_MOVESET_OVERRIDE: Moves | Array = []; - readonly OPP_SHINY_OVERRIDE: boolean = false; - readonly OPP_VARIANT_OVERRIDE: Variant = 0; + readonly OPP_SHINY_OVERRIDE: boolean | null = null; + readonly OPP_VARIANT_OVERRIDE: Variant | null = null; readonly OPP_IVS_OVERRIDE: number | number[] = []; readonly OPP_FORM_OVERRIDES: Partial> = {}; /** @@ -156,6 +156,7 @@ class DefaultOverrides { readonly EGG_VARIANT_OVERRIDE: VariantTier | null = null; readonly EGG_FREE_GACHA_PULLS_OVERRIDE: boolean = false; readonly EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0; + readonly UNLIMITED_EGG_COUNT_OVERRIDE: boolean = false; // ------------------------- // MYSTERY ENCOUNTER OVERRIDES diff --git a/src/phase.ts b/src/phase.ts index 02939757112..5cf91f2c478 100644 --- a/src/phase.ts +++ b/src/phase.ts @@ -8,7 +8,6 @@ export class Phase { } start() { - console.log(`%cStart Phase ${this.constructor.name}`, "color:green;"); if (this.scene.abilityBar.shown) { this.scene.abilityBar.resetAutoHideTimer(); } diff --git a/src/phases/check-status-effect-phase.ts b/src/phases/check-status-effect-phase.ts new file mode 100644 index 00000000000..44918b54966 --- /dev/null +++ b/src/phases/check-status-effect-phase.ts @@ -0,0 +1,23 @@ +import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase"; +import { Phase } from "#app/phase"; +import { BattlerIndex } from "#app/battle"; +import BattleScene from "#app/battle-scene"; + +export class CheckStatusEffectPhase extends Phase { + private order : BattlerIndex[]; + constructor(scene : BattleScene, order : BattlerIndex[]) { + super(scene); + this.scene = scene; + this.order = order; + } + + start() { + const field = this.scene.getField(); + for (const o of this.order) { + if (field[o].status && field[o].status.isPostTurn()) { + this.scene.unshiftPhase(new PostTurnStatusEffectPhase(this.scene, o)); + } + } + this.end(); + } +} diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index e85c66543ac..e6f2eb69ff3 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -84,170 +84,170 @@ export class CommandPhase extends FieldPhase { let success: boolean; switch (command) { - case Command.FIGHT: - let useStruggle = false; - if (cursor === -1 || + case Command.FIGHT: + let useStruggle = false; + if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean) || (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) { - const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? - const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; - const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; - if (!moveId) { - turnCommand.targets = [ this.fieldIndex ]; - } - console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); - if (moveTargets.targets.length > 1 && moveTargets.multiple) { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); - } - if (moveTargets.targets.length <= 1 || moveTargets.multiple) { + const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? + const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; + const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; + if (!moveId) { + turnCommand.targets = [ this.fieldIndex ]; + } + console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); + if (moveTargets.targets.length > 1 && moveTargets.multiple) { + this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + } + if (moveTargets.targets.length <= 1 || moveTargets.multiple) { turnCommand.move!.targets = moveTargets.targets; //TODO: is the bang correct here? - } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { + } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { turnCommand.move!.targets = playerPokemon.getMoveQueue()[0].targets; //TODO: is the bang correct here? - } else { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); - } - this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; - success = true; - } else if (cursor < playerPokemon.getMoveset().length) { - const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? - this.scene.ui.setMode(Mode.MESSAGE); + } else { + this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + } + this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; + success = true; + } else if (cursor < playerPokemon.getMoveset().length) { + const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? + this.scene.ui.setMode(Mode.MESSAGE); - // Decides between a Disabled, Not Implemented, or No PP translation message - const errorMessage = - playerPokemon.isMoveRestricted(move.moveId) - ? playerPokemon.getRestrictingTag(move.moveId)!.selectionDeniedText(playerPokemon, move.moveId) + // Decides between a Disabled, Not Implemented, or No PP translation message + const errorMessage = + playerPokemon.isMoveRestricted(move.moveId, playerPokemon) + ? playerPokemon.getRestrictingTag(move.moveId, playerPokemon)!.selectionDeniedText(playerPokemon, move.moveId) : move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP"; - const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator + const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator - this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); - }, null, true); - } - break; - case Command.BALL: - const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1); - if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (this.scene.currentBattle.isBattleMysteryEncounter() && !this.scene.currentBattle.mysteryEncounter!.catchAllowed) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballMysteryEncounter"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else { - const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); - if (targets.length > 1) { + this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); + }, null, true); + } + break; + case Command.BALL: + const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1); + if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { + this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { this.scene.ui.showText("", 0); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); - } else if (cursor < 5) { - const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); - if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { + } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (this.scene.currentBattle.isBattleMysteryEncounter() && !this.scene.currentBattle.mysteryEncounter!.catchAllowed) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballMysteryEncounter"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); + if (targets.length > 1) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { + this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { this.scene.ui.showText("", 0); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); - } else { - this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; + } else if (cursor < 5) { + const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); + if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; this.scene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; if (this.fieldIndex) { this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; } success = true; + } } } - } - break; - case Command.POKEMON: - case Command.RUN: - const isSwitch = command === Command.POKEMON; - const { currentBattle, arena } = this.scene; - const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; - if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { - this.scene.ui.showText("", 0); + break; + case Command.POKEMON: + case Command.RUN: + const isSwitch = command === Command.POKEMON; + const { currentBattle, arena } = this.scene; + const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; + if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (!isSwitch && (currentBattle.battleType === BattleType.TRAINER || currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else { - const batonPass = isSwitch && args[0] as boolean; - const trappedAbMessages: string[] = []; - if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) { - currentBattle.turnCommands[this.fieldIndex] = isSwitch - ? { command: Command.POKEMON, cursor: cursor, args: args } - : { command: Command.RUN }; - success = true; - if (!isSwitch && this.fieldIndex) { - currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; - } - } else if (trappedAbMessages.length > 0) { - if (!isSwitch) { - this.scene.ui.setMode(Mode.MESSAGE); - } - this.scene.ui.showText(trappedAbMessages[0], null, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { this.scene.ui.showText("", 0); - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (!isSwitch && (currentBattle.battleType === BattleType.TRAINER || currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); } else { - const trapTag = playerPokemon.getTag(TrappedTag); - - // trapTag should be defined at this point, but just in case... - if (!trapTag) { + const batonPass = isSwitch && args[0] as boolean; + const trappedAbMessages: string[] = []; + if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) { currentBattle.turnCommands[this.fieldIndex] = isSwitch ? { command: Command.POKEMON, cursor: cursor, args: args } : { command: Command.RUN }; - break; - } - - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - } - this.scene.ui.showText( - i18next.t("battle:noEscapePokemon", { - pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "", - moveName: trapTag.getMoveName(), - escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee") - }), - null, - () => { + success = true; + if (!isSwitch && this.fieldIndex) { + currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; + } + } else if (trappedAbMessages.length > 0) { + if (!isSwitch) { + this.scene.ui.setMode(Mode.MESSAGE); + } + this.scene.ui.showText(trappedAbMessages[0], null, () => { this.scene.ui.showText("", 0); if (!isSwitch) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); } }, null, true); + } else { + const trapTag = playerPokemon.getTag(TrappedTag); + + // trapTag should be defined at this point, but just in case... + if (!trapTag) { + currentBattle.turnCommands[this.fieldIndex] = isSwitch + ? { command: Command.POKEMON, cursor: cursor, args: args } + : { command: Command.RUN }; + break; + } + + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + } + this.scene.ui.showText( + i18next.t("battle:noEscapePokemon", { + pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "", + moveName: trapTag.getMoveName(), + escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee") + }), + null, + () => { + this.scene.ui.showText("", 0); + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + }, null, true); + } } - } - break; + break; } if (success!) { // TODO: is the bang correct? diff --git a/src/phases/damage-phase.ts b/src/phases/damage-phase.ts index 66b11512729..44e3dfd4182 100644 --- a/src/phases/damage-phase.ts +++ b/src/phases/damage-phase.ts @@ -41,16 +41,16 @@ export class DamagePhase extends PokemonPhase { applyDamage() { switch (this.damageResult) { - case HitResult.EFFECTIVE: - this.scene.playSound("se/hit"); - break; - case HitResult.SUPER_EFFECTIVE: - case HitResult.ONE_HIT_KO: - this.scene.playSound("se/hit_strong"); - break; - case HitResult.NOT_VERY_EFFECTIVE: - this.scene.playSound("se/hit_weak"); - break; + case HitResult.EFFECTIVE: + this.scene.playSound("se/hit"); + break; + case HitResult.SUPER_EFFECTIVE: + case HitResult.ONE_HIT_KO: + this.scene.playSound("se/hit_strong"); + break; + case HitResult.NOT_VERY_EFFECTIVE: + this.scene.playSound("se/hit_weak"); + break; } if (this.amount) { diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts index d81d63696a5..4c57be09b79 100644 --- a/src/phases/egg-lapse-phase.ts +++ b/src/phases/egg-lapse-phase.ts @@ -33,7 +33,7 @@ export class EggLapsePhase extends Phase { if (eggsToHatchCount > 0) { if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) { this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => { - // show prompt for skip + // show prompt for skip, blocking inputs for 1 second this.scene.ui.showText(i18next.t("battle:eggSkipPrompt"), 0); this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { this.hatchEggsSkipped(eggsToHatch); @@ -41,7 +41,8 @@ export class EggLapsePhase extends Phase { }, () => { this.hatchEggsRegular(eggsToHatch); this.end(); - } + }, + null, null, null, 1000, true ); }, 100, true); } else if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 2) { diff --git a/src/phases/egg-summary-phase.ts b/src/phases/egg-summary-phase.ts index 75c6939daf1..b673eb4887b 100644 --- a/src/phases/egg-summary-phase.ts +++ b/src/phases/egg-summary-phase.ts @@ -1,7 +1,6 @@ import BattleScene from "#app/battle-scene"; import { Phase } from "#app/phase"; import { Mode } from "#app/ui/ui"; -import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler"; import { EggHatchData } from "#app/data/egg-hatch-data"; /** @@ -11,7 +10,6 @@ import { EggHatchData } from "#app/data/egg-hatch-data"; */ export class EggSummaryPhase extends Phase { private eggHatchData: EggHatchData[]; - private eggHatchHandler: EggHatchSceneHandler; constructor(scene: BattleScene, eggHatchData: EggHatchData[]) { super(scene); @@ -26,7 +24,6 @@ export class EggSummaryPhase extends Phase { if (i >= this.eggHatchData.length) { this.scene.ui.setModeForceTransition(Mode.EGG_HATCH_SUMMARY, this.eggHatchData).then(() => { this.scene.fadeOutBgm(undefined, false); - this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler; }); } else { diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 3610ffed386..b7071c4cc6f 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -35,6 +35,7 @@ import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-d import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { getGoldenBugNetSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { Biome } from "#enums/biome"; +import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; export class EncounterPhase extends BattlePhase { private loaded: boolean; @@ -68,7 +69,7 @@ export class EncounterPhase extends BattlePhase { this.scene.executeWithSeedOffset(() => { const currentSessionEncounterType = battle.mysteryEncounterType; battle.mysteryEncounter = this.scene.getMysteryEncounter(currentSessionEncounterType); - }, battle.waveIndex << 4); + }, battle.waveIndex * 16); } const mysteryEncounter = battle.mysteryEncounter; if (mysteryEncounter) { @@ -251,6 +252,13 @@ export class EncounterPhase extends BattlePhase { this.scene.updateModifiers(true); }*/ + const { battleType, waveIndex } = this.scene.currentBattle; + if (this.scene.isMysteryEncounterValidForWave(battleType, waveIndex) && !this.scene.currentBattle.isBattleMysteryEncounter()) { + // Increment ME spawn chance if an ME could have spawned but did not + // Only do this AFTER session has been saved to avoid duplicating increments + this.scene.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS; + } + for (const pokemon of this.scene.getParty()) { if (pokemon) { pokemon.resetBattleData(); @@ -494,31 +502,31 @@ export class EncounterPhase extends BattlePhase { tryOverrideForBattleSpec(): boolean { switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - const enemy = this.scene.getEnemyPokemon(); - this.scene.ui.showText(this.getEncounterMessage(), null, () => { - const localizationKey = "battleSpecDialogue:encounter"; - if (this.scene.ui.shouldSkipDialogue(localizationKey)) { + case BattleSpec.FINAL_BOSS: + const enemy = this.scene.getEnemyPokemon(); + this.scene.ui.showText(this.getEncounterMessage(), null, () => { + const localizationKey = "battleSpecDialogue:encounter"; + if (this.scene.ui.shouldSkipDialogue(localizationKey)) { // Logging mirrors logging found in dialogue-ui-handler - console.log(`Dialogue ${localizationKey} skipped`); - this.doEncounterCommon(false); - } else { - const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed; - // The line below checks if an English ordinal is necessary or not based on whether an entry for encounterLocalizationKey exists in the language or not. - const ordinalUsed = !i18next.exists(localizationKey, { fallbackLng: []}) || i18next.resolvedLanguage === "en" ? i18next.t("battleSpecDialogue:key", { count: count, ordinal: true }) : ""; - const cycleCount = count.toLocaleString() + ordinalUsed; - const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; - const genderStr = PlayerGender[genderIndex].toLowerCase(); - const encounterDialogue = i18next.t(localizationKey, { context: genderStr, cycleCount: cycleCount }); - if (!this.scene.gameData.getSeenDialogues()[localizationKey]) { - this.scene.gameData.saveSeenDialogue(localizationKey); - } - this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { + console.log(`Dialogue ${localizationKey} skipped`); this.doEncounterCommon(false); - }); - } - }, 1500, true); - return true; + } else { + const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed; + // The line below checks if an English ordinal is necessary or not based on whether an entry for encounterLocalizationKey exists in the language or not. + const ordinalUsed = !i18next.exists(localizationKey, { fallbackLng: []}) || i18next.resolvedLanguage === "en" ? i18next.t("battleSpecDialogue:key", { count: count, ordinal: true }) : ""; + const cycleCount = count.toLocaleString() + ordinalUsed; + const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; + const genderStr = PlayerGender[genderIndex].toLowerCase(); + const encounterDialogue = i18next.t(localizationKey, { context: genderStr, cycleCount: cycleCount }); + if (!this.scene.gameData.getSeenDialogues()[localizationKey]) { + this.scene.gameData.saveSeenDialogue(localizationKey); + } + this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { + this.doEncounterCommon(false); + }); + } + }, 1500, true); + return true; } return false; } diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 66bb22899be..eee1fd52938 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -1,12 +1,12 @@ import BattleScene from "#app/battle-scene"; import { BattlerIndex, BattleType } from "#app/battle"; import { applyPostFaintAbAttrs, PostFaintAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr } from "#app/data/ability"; -import { BattlerTagLapseType } from "#app/data/battler-tags"; +import { BattlerTagLapseType, DestinyBondTag } from "#app/data/battler-tags"; import { battleSpecDialogue } from "#app/data/dialogue"; import { allMoves, PostVictoryStatStageChangeAttr } from "#app/data/move"; import { BattleSpec } from "#app/enums/battle-spec"; import { StatusEffect } from "#app/enums/status-effect"; -import { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon"; +import Pokemon, { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import i18next from "i18next"; @@ -19,19 +19,40 @@ import { SwitchPhase } from "./switch-phase"; import { VictoryPhase } from "./victory-phase"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { SwitchType } from "#enums/switch-type"; +import { isNullOrUndefined } from "#app/utils"; +import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters"; export class FaintPhase extends PokemonPhase { + /** + * Whether or not enduring (for this phase's purposes, Reviver Seed) should be prevented + */ private preventEndure: boolean; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure?: boolean) { + /** + * Destiny Bond tag belonging to the currently fainting Pokemon, if applicable + */ + private destinyTag?: DestinyBondTag; + + /** + * The source Pokemon that dealt fatal damage and should get KO'd by Destiny Bond, if applicable + */ + private source?: Pokemon; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure: boolean = false, destinyTag?: DestinyBondTag, source?: Pokemon) { super(scene, battlerIndex); - this.preventEndure = preventEndure!; // TODO: is this bang correct? + this.preventEndure = preventEndure; + this.destinyTag = destinyTag; + this.source = source; } start() { super.start(); + if (!isNullOrUndefined(this.destinyTag) && !isNullOrUndefined(this.source)) { + this.destinyTag.lapse(this.source, BattlerTagLapseType.CUSTOM); + } + if (!this.preventEndure) { const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, this.getPokemon()) as PokemonInstantReviveModifier; @@ -127,7 +148,7 @@ export class FaintPhase extends PokemonPhase { pokemon.faintCry(() => { if (pokemon instanceof PlayerPokemon) { - pokemon.addFriendship(-10); + pokemon.addFriendship(-FRIENDSHIP_LOSS_FROM_FAINT); } pokemon.hideInfo(); this.scene.playSound("se/faint"); @@ -158,19 +179,19 @@ export class FaintPhase extends PokemonPhase { tryOverrideForBattleSpec(): boolean { switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - if (!this.player) { - const enemy = this.getPokemon(); - if (enemy.formIndex) { - this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); - } else { + case BattleSpec.FINAL_BOSS: + if (!this.player) { + const enemy = this.getPokemon(); + if (enemy.formIndex) { + this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); + } else { // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase - enemy.hp++; - this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); - this.end(); + enemy.hp++; + this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); + this.end(); + } + return true; } - return true; - } } return false; diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index 6480577258a..fefda384092 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -2,24 +2,37 @@ import BattleScene from "#app/battle-scene"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import Move, { allMoves } from "#app/data/move"; import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms"; -import { Moves } from "#app/enums/moves"; +import { Moves } from "#enums/moves"; import { getPokemonNameWithAffix } from "#app/messages"; +import Overrides from "#app/overrides"; import EvolutionSceneHandler from "#app/ui/evolution-scene-handler"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; -import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; +import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import Pokemon from "#app/field/pokemon"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; + +export enum LearnMoveType { + /** For learning a move via level-up, evolution, or other non-item-based event */ + LEARN_MOVE, + /** For learning a move via Memory Mushroom */ + MEMORY, + /** For learning a move via TM */ + TM +} export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { private moveId: Moves; private messageMode: Mode; - private fromTM: boolean; + private learnMoveType; + private cost: number; - constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, fromTM?: boolean) { + constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, learnMoveType: LearnMoveType = LearnMoveType.LEARN_MOVE, cost: number = -1) { super(scene, partyMemberIndex); this.moveId = moveId; - this.fromTM = fromTM ?? false; + this.learnMoveType = learnMoveType; + this.cost = cost; } start() { @@ -136,22 +149,37 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { * @param Pokemon The Pokemon learning the move */ async learnMove(index: number, move: Move, pokemon: Pokemon, textMessage?: string) { - if (this.fromTM) { + if (this.learnMoveType === LearnMoveType.TM) { if (!pokemon.usedTMs) { pokemon.usedTMs = []; } pokemon.usedTMs.push(this.moveId); + this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase); + } else if (this.learnMoveType === LearnMoveType.MEMORY) { + if (this.cost !== -1) { + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + this.scene.money -= this.cost; + this.scene.updateMoneyText(); + this.scene.animateMoneyChanged(false); + } + this.scene.playSound("se/buy"); + } else { + this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase); + } } pokemon.setMove(index, this.moveId); initMoveAnim(this.scene, this.moveId).then(() => { loadMoveAnimAssets(this.scene, [ this.moveId ], true); - this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is }); this.scene.ui.setMode(this.messageMode); const learnMoveText = i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }); - textMessage = textMessage ? textMessage + "$" + learnMoveText : learnMoveText; - await this.scene.ui.showTextPromise(textMessage, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true); - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); - this.end(); + if (textMessage) { + await this.scene.ui.showTextPromise(textMessage); + } + this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is + this.scene.ui.showText(learnMoveText, null, () => { + this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); + this.end(); + }, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true); } } diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index a99e038acba..a2fa8a16533 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -28,7 +28,7 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { this.scene.gameData.gameStats.highestLevel = this.level; } - this.scene.validateAchvs(LevelAchv, new Utils.IntegerHolder(this.level)); + this.scene.validateAchvs(LevelAchv, new Utils.NumberHolder(this.level)); const pokemon = this.getPokemon(); const prevStats = pokemon.stats.slice(0); diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index b2d429a4313..dc880f85e23 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag"; import { MoveAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags"; -import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move"; +import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, OneHitKOAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Moves } from "#app/enums/moves"; @@ -99,8 +99,9 @@ export class MoveEffectPhase extends PokemonPhase { const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); const hasActiveTargets = targets.some(t => t.isActive(true)); - /** Check if the target is immune via ability to the attacking move */ - const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)); + /** Check if the target is immune via ability to the attacking move, and NOT in semi invulnerable state */ + const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)) + && !targets[0].getTag(SemiInvulnerableTag); /** * If no targets are left for the move to hit (FAIL), or the invoked move is single-target @@ -140,7 +141,7 @@ export class MoveEffectPhase extends PokemonPhase { const bypassIgnoreProtect = new Utils.BooleanHolder(false); /** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */ if (!this.move.getMove().isAllyTarget()) { - this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect); + this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, false, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect); } /** Is the target protected by Protect, etc. or a relevant conditional protection effect? */ @@ -148,8 +149,9 @@ export class MoveEffectPhase extends PokemonPhase { && (hasConditionalProtectApplied.value || (!target.findTags(t => t instanceof DamageProtectedTag).length && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))) || (this.move.getMove().category !== MoveCategory.STATUS && target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType)))); - /** Is the pokemon immune due to an ablility? */ - const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)); + /** Is the pokemon immune due to an ablility, and also not in a semi invulnerable state? */ + const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)) + && !target.getTag(SemiInvulnerableTag); /** * If the move missed a target, stop all future hits against that target @@ -404,6 +406,10 @@ export class MoveEffectPhase extends PokemonPhase { return true; } + if (target.getTag(BattlerTagType.TELEKINESIS) && !target.getTag(SemiInvulnerableTag) && !this.move.getMove().hasAttr(OneHitKOAttr)) { + return true; + } + const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType) diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 10cc062ea3b..0af61918636 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -8,10 +8,6 @@ import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { Type } from "#app/data/type"; import { getTerrainBlockMessage } from "#app/data/weather"; -import { Abilities } from "#app/enums/abilities"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; -import { Moves } from "#app/enums/moves"; -import { StatusEffect } from "#app/enums/status-effect"; import { MoveUsedEvent } from "#app/events/battle-scene"; import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -20,7 +16,11 @@ import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; -import * as Utils from "#app/utils"; +import { BooleanHolder, NumberHolder } from "#app/utils"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Moves } from "#enums/moves"; +import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; export class MovePhase extends BattlePhase { @@ -89,7 +89,7 @@ export class MovePhase extends BattlePhase { this.cancelled = true; } - public start() { + public start(): void { super.start(); console.log(Moves[this.move.moveId]); @@ -128,7 +128,9 @@ export class MovePhase extends BattlePhase { this.lapsePreMoveAndMoveTags(); - this.resolveFinalPreMoveCancellationChecks(); + if (!(this.failed || this.cancelled)) { + this.resolveFinalPreMoveCancellationChecks(); + } if (this.cancelled || this.failed) { this.handlePreMoveFailures(); @@ -140,47 +142,48 @@ export class MovePhase extends BattlePhase { } /** Check for cancellation edge cases - no targets remaining, or {@linkcode Moves.NONE} is in the queue */ - protected resolveFinalPreMoveCancellationChecks() { + protected resolveFinalPreMoveCancellationChecks(): void { const targets = this.getActiveTargetPokemon(); const moveQueue = this.pokemon.getMoveQueue(); if (targets.length === 0 || (moveQueue.length && moveQueue[0].move === Moves.NONE)) { + this.showMoveText(); this.showFailedText(); - this.cancelled = true; + this.cancel(); } } - public getActiveTargetPokemon() { + public getActiveTargetPokemon(): Pokemon[] { return this.scene.getField(true).filter(p => this.targets.includes(p.getBattlerIndex())); } /** * Handles {@link StatusEffect.SLEEP Sleep}/{@link StatusEffect.PARALYSIS Paralysis}/{@link StatusEffect.FREEZE Freeze} rolls and side effects. */ - protected resolvePreMoveStatusEffects() { + protected resolvePreMoveStatusEffects(): void { if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) { this.pokemon.status.incrementTurn(); let activated = false; let healed = false; switch (this.pokemon.status.effect) { - case StatusEffect.PARALYSIS: - if (!this.pokemon.randSeedInt(4)) { - activated = true; - this.cancelled = true; - } - break; - case StatusEffect.SLEEP: - applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); - healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; - activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); - this.cancelled = activated; - break; - case StatusEffect.FREEZE: - healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); - activated = !healed; - this.cancelled = activated; - break; + case StatusEffect.PARALYSIS: + if (!this.pokemon.randSeedInt(4)) { + activated = true; + this.cancelled = true; + } + break; + case StatusEffect.SLEEP: + applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); + healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; + activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); + this.cancelled = activated; + break; + case StatusEffect.FREEZE: + healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); + activated = !healed; + this.cancelled = activated; + break; } if (activated) { @@ -198,7 +201,7 @@ export class MovePhase extends BattlePhase { * Lapse {@linkcode BattlerTagLapseType.PRE_MOVE PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed. * Also lapse {@linkcode BattlerTagLapseType.MOVE MOVE} tags if the move should be successful. */ - protected lapsePreMoveAndMoveTags() { + protected lapsePreMoveAndMoveTags(): void { this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE); // TODO: does this intentionally happen before the no targets/Moves.NONE on queue cancellation case is checked? @@ -207,7 +210,7 @@ export class MovePhase extends BattlePhase { } } - protected useMove() { + protected useMove(): void { const targets = this.getActiveTargetPokemon(); const moveQueue = this.pokemon.getMoveQueue(); @@ -217,7 +220,8 @@ export class MovePhase extends BattlePhase { this.showMoveText(); // TODO: Clean up implementation of two-turn moves. - if (moveQueue.length > 0) { // Using .shift here clears out two turn moves once they've been used + if (moveQueue.length > 0) { + // Using .shift here clears out two turn moves once they've been used this.ignorePp = moveQueue.shift()?.ignorePP ?? false; } @@ -226,7 +230,7 @@ export class MovePhase extends BattlePhase { const ppUsed = 1 + this.getPpIncreaseFromPressure(targets); this.move.usePp(ppUsed); - this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed)); + this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); } // Update the battle's "last move" pointer, unless we're currently mimicking a move. @@ -275,7 +279,7 @@ export class MovePhase extends BattlePhase { this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual }); let failedText: string | undefined; - const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new Utils.BooleanHolder(false)); + const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new BooleanHolder(false)); if (failureMessage) { failedText = failureMessage; @@ -299,7 +303,7 @@ export class MovePhase extends BattlePhase { * Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`, * then ends the phase. */ - public end() { + public end(): void { if (!this.followUp && this.canMove()) { this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex())); } @@ -313,7 +317,7 @@ export class MovePhase extends BattlePhase { * * TODO: This hardcodes the PP increase at 1 per opponent, rather than deferring to the ability. */ - public getPpIncreaseFromPressure(targets: Pokemon[]) { + public getPpIncreaseFromPressure(targets: Pokemon[]): number { const foesWithPressure = this.pokemon.getOpponents().filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr)); return foesWithPressure.length; } @@ -323,10 +327,10 @@ export class MovePhase extends BattlePhase { * - Move redirection abilities, effects, etc. * - Counterattacks, which pass a special value into the `targets` constructor param (`[`{@linkcode BattlerIndex.ATTACKER}`]`). */ - protected resolveRedirectTarget() { + protected resolveRedirectTarget(): void { if (this.targets.length === 1) { const currentTarget = this.targets[0]; - const redirectTarget = new Utils.NumberHolder(currentTarget); + const redirectTarget = new NumberHolder(currentTarget); // check move redirection abilities of every pokemon *except* the user. this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget)); @@ -372,7 +376,7 @@ export class MovePhase extends BattlePhase { * If there is no last attacker, or they are no longer on the field, a message is displayed and the * move is marked for failure. */ - protected resolveCounterAttackTarget() { + protected resolveCounterAttackTarget(): void { if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) { if (this.pokemon.turnData.attacksReceived.length) { this.targets[0] = this.pokemon.turnData.attacksReceived[0].sourceBattlerIndex; @@ -411,7 +415,7 @@ export class MovePhase extends BattlePhase { * * TODO: handle charge moves more gracefully */ - protected handlePreMoveFailures() { + protected handlePreMoveFailures(): void { if (this.cancelled || this.failed) { if (this.failed) { const ppUsed = this.ignorePp ? 0 : 1; diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 0166b2d6abd..49e78fa5369 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -402,7 +402,7 @@ export class MysteryEncounterBattlePhase extends Phase { } } - const availablePartyMembers = scene.getParty().filter(p => !p.isFainted()); + const availablePartyMembers = scene.getParty().filter(p => p.isAllowedInBattle()); if (!availablePartyMembers[0].isOnField()) { scene.pushPhase(new SummonPhase(scene, 0)); diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index bf38c432394..c396fa7ba59 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -6,7 +6,6 @@ import { StatusEffect } from "#app/enums/status-effect"; import Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonPhase } from "./pokemon-phase"; -import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase"; export class ObtainStatusEffectPhase extends PokemonPhase { private statusEffect?: StatusEffect | undefined; @@ -33,9 +32,6 @@ export class ObtainStatusEffectPhase extends PokemonPhase { pokemon.updateInfo(true); new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => { this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined)); - if (pokemon.status?.isPostTurn()) { - this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex)); - } this.end(); }); return; diff --git a/src/phases/outdated-phase.ts b/src/phases/outdated-phase.ts deleted file mode 100644 index 4baf16d2f56..00000000000 --- a/src/phases/outdated-phase.ts +++ /dev/null @@ -1,13 +0,0 @@ -import BattleScene from "#app/battle-scene"; -import { Phase } from "#app/phase"; -import { Mode } from "#app/ui/ui"; - -export class OutdatedPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - this.scene.ui.setMode(Mode.OUTDATED); - } -} diff --git a/src/phases/pokemon-anim-phase.ts b/src/phases/pokemon-anim-phase.ts index 6f1bfe8bc18..26ae11d1026 100644 --- a/src/phases/pokemon-anim-phase.ts +++ b/src/phases/pokemon-anim-phase.ts @@ -25,20 +25,20 @@ export class PokemonAnimPhase extends BattlePhase { super.start(); switch (this.key) { - case PokemonAnimType.SUBSTITUTE_ADD: - this.doSubstituteAddAnim(); - break; - case PokemonAnimType.SUBSTITUTE_PRE_MOVE: - this.doSubstitutePreMoveAnim(); - break; - case PokemonAnimType.SUBSTITUTE_POST_MOVE: - this.doSubstitutePostMoveAnim(); - break; - case PokemonAnimType.SUBSTITUTE_REMOVE: - this.doSubstituteRemoveAnim(); - break; - default: - this.end(); + case PokemonAnimType.SUBSTITUTE_ADD: + this.doSubstituteAddAnim(); + break; + case PokemonAnimType.SUBSTITUTE_PRE_MOVE: + this.doSubstitutePreMoveAnim(); + break; + case PokemonAnimType.SUBSTITUTE_POST_MOVE: + this.doSubstitutePostMoveAnim(); + break; + case PokemonAnimType.SUBSTITUTE_REMOVE: + this.doSubstituteRemoveAnim(); + break; + default: + this.end(); } } diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts index b99c0b90fd8..617bb8b1cfe 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -20,7 +20,7 @@ export class PostSummonPhase extends PokemonPhase { if (pokemon.status?.effect === StatusEffect.TOXIC) { pokemon.status.turnCount = 0; } - this.scene.arena.applyTags(ArenaTrapTag, pokemon); + this.scene.arena.applyTags(ArenaTrapTag, false, pokemon); // If this is mystery encounter and has post summon phase tag, apply post summon effects if (this.scene.currentBattle.isBattleMysteryEncounter() && pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag).length > 0) { diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 285bbddde88..06681b733f0 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -26,16 +26,16 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); const damage = new Utils.NumberHolder(0); switch (pokemon.status.effect) { - case StatusEffect.POISON: - damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); - break; - case StatusEffect.TOXIC: - damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); - break; - case StatusEffect.BURN: - damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); - applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); - break; + case StatusEffect.POISON: + damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); + break; + case StatusEffect.TOXIC: + damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); + break; + case StatusEffect.BURN: + damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); + applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); + break; } if (damage.value) { // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 159af979fa0..38d5cfb4a10 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -16,26 +16,32 @@ export class SelectModifierPhase extends BattlePhase { private rerollCount: integer; private modifierTiers?: ModifierTier[]; private customModifierSettings?: CustomModifierSettings; + private isCopy: boolean; - constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings) { + private typeOptions: ModifierTypeOption[]; + + constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings, isCopy: boolean = false) { super(scene); this.rerollCount = rerollCount; this.modifierTiers = modifierTiers; this.customModifierSettings = customModifierSettings; + this.isCopy = isCopy; } start() { super.start(); - if (!this.rerollCount) { + if (!this.rerollCount && !this.isCopy) { this.updateSeed(); - } else { + } else if (this.rerollCount) { this.scene.reroll = false; } const party = this.scene.getParty(); - regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); + if (!this.isCopy) { + regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); + } const modifierCount = new Utils.IntegerHolder(3); if (this.isPlayer()) { this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); @@ -54,7 +60,7 @@ export class SelectModifierPhase extends BattlePhase { } } - const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value); + this.typeOptions = this.getModifierTypeOptions(modifierCount.value); const modifierSelectCallback = (rowCursor: integer, cursor: integer) => { if (rowCursor < 0 || cursor < 0) { @@ -63,86 +69,86 @@ export class SelectModifierPhase extends BattlePhase { this.scene.ui.revertMode(); this.scene.ui.setMode(Mode.MESSAGE); super.end(); - }, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers))); + }, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers))); }); return false; } let modifierType: ModifierType; let cost: integer; - const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers); + const rerollCost = this.getRerollCost(this.scene.lockModifierTiers); switch (rowCursor) { - case 0: - switch (cursor) { case 0: - if (rerollCost < 0 || this.scene.money < rerollCost) { - this.scene.ui.playError(); - return false; - } else { - this.scene.reroll = true; - this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); - if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - this.scene.money -= rerollCost; - this.scene.updateMoneyText(); - this.scene.animateMoneyChanged(false); - } - this.scene.playSound("se/buy"); - } - break; - case 1: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { - if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { - const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + switch (cursor) { + case 0: + if (rerollCost < 0 || this.scene.money < rerollCost) { + this.scene.ui.playError(); + return false; + } else { + this.scene.reroll = true; + this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + this.scene.money -= rerollCost; + this.scene.updateMoneyText(); + this.scene.animateMoneyChanged(false); + } + this.scene.playSound("se/buy"); + } + break; + case 1: + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { + if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { + const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; - const itemModifier = itemModifiers[itemIndex]; - this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); - } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - } - }, PartyUiHandler.FilterItemMaxStacks); - break; - case 2: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - }); - break; - case 3: - if (rerollCost < 0) { - // Reroll lock button is also disabled when reroll is disabled - this.scene.ui.playError(); - return false; + const itemModifier = itemModifiers[itemIndex]; + this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); + } else { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); + } + }, PartyUiHandler.FilterItemMaxStacks); + break; + case 2: + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); + }); + break; + case 3: + if (rerollCost < 0) { + // Reroll lock button is also disabled when reroll is disabled + this.scene.ui.playError(); + return false; + } + this.scene.lockModifierTiers = !this.scene.lockModifierTiers; + const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; + uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers)); + uiHandler.updateLockRaritiesText(); + uiHandler.updateRerollCostText(); + return false; } - this.scene.lockModifierTiers = !this.scene.lockModifierTiers; - const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - uiHandler.updateLockRaritiesText(); - uiHandler.updateRerollCostText(); - return false; - } - return true; - case 1: - if (typeOptions.length === 0) { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE); - super.end(); return true; - } - if (typeOptions[cursor].type) { - modifierType = typeOptions[cursor].type; - } - break; - default: - const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); - const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; - if (shopOption.type) { - modifierType = shopOption.type; - } - // Apply Black Sludge to healing item cost - const healingItemCost = new NumberHolder(shopOption.cost); - this.scene.applyModifier(HealShopCostModifier, true, healingItemCost); - cost = healingItemCost.value; - break; + case 1: + if (this.typeOptions.length === 0) { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.MESSAGE); + super.end(); + return true; + } + if (this.typeOptions[cursor].type) { + modifierType = this.typeOptions[cursor].type; + } + break; + default: + const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); + const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; + if (shopOption.type) { + modifierType = shopOption.type; + } + // Apply Black Sludge to healing item cost + const healingItemCost = new NumberHolder(shopOption.cost); + this.scene.applyModifier(HealShopCostModifier, true, healingItemCost); + cost = healingItemCost.value; + break; } if (cost! && (this.scene.money < cost) && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { // TODO: is the bang on cost correct? @@ -151,8 +157,16 @@ export class SelectModifierPhase extends BattlePhase { } const applyModifier = (modifier: Modifier, playSound: boolean = false) => { - const result = this.scene.addModifier(modifier, false, playSound); - if (cost) { + const result = this.scene.addModifier(modifier, false, playSound, undefined, undefined, cost); + // Queue a copy of this phase when applying a TM or Memory Mushroom. + // If the player selects either of these, then escapes out of consuming them, + // they are returned to a shop in the same state. + if (modifier.type instanceof RememberMoveModifierType || + modifier.type instanceof TmModifierType) { + this.scene.unshiftPhase(this.copy()); + } + + if (cost && !(modifier.type instanceof RememberMoveModifierType)) { result.then(success => { if (success) { if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { @@ -189,7 +203,7 @@ export class SelectModifierPhase extends BattlePhase { applyModifier(modifier, true); }); } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); } }, modifierType.selectFilter); } else { @@ -216,7 +230,7 @@ export class SelectModifierPhase extends BattlePhase { applyModifier(modifier!, true); // TODO: is the bang correct? }); } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); } }, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier); } @@ -226,7 +240,7 @@ export class SelectModifierPhase extends BattlePhase { return !cost!;// TODO: is the bang correct? }; - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); } updateSeed(): void { @@ -237,13 +251,13 @@ export class SelectModifierPhase extends BattlePhase { return true; } - getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): number { + getRerollCost(lockRarities: boolean): number { let baseValue = 0; if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) { return baseValue; } else if (lockRarities) { const tierValues = [ 50, 125, 300, 750, 2000 ]; - for (const opt of typeOptions) { + for (const opt of this.typeOptions) { baseValue += tierValues[opt.type.tier ?? 0]; } } else { @@ -271,6 +285,16 @@ export class SelectModifierPhase extends BattlePhase { return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined, this.customModifierSettings); } + copy(): SelectModifierPhase { + return new SelectModifierPhase( + this.scene, + this.rerollCount, + this.modifierTiers, + { guaranteedModifierTypeOptions: this.typeOptions, rerollMultiplier: this.customModifierSettings?.rerollMultiplier, allowLuckUpgrades: false }, + true + ); + } + addModifier(modifier: Modifier): Promise { return this.scene.addModifier(modifier, false, true); } diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 4418c38c849..4c13b883445 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -36,6 +36,16 @@ export class StatStageChangePhase extends PokemonPhase { } start() { + + // Check if multiple stats are being changed at the same time, then run SSCPhase for each of them + if (this.stats.length > 1) { + for (let i = 0; i < this.stats.length; i++) { + const stat = [ this.stats[i] ]; + this.scene.unshiftPhase(new StatStageChangePhase(this.scene, this.battlerIndex, this.selfTarget, stat, this.stages, this.showMessage, this.ignoreAbilities, this.canBeCopied, this.onChange)); + } + return this.end(); + } + const pokemon = this.getPokemon(); if (!pokemon.isActive(true)) { @@ -54,8 +64,7 @@ export class StatStageChangePhase extends PokemonPhase { const cancelled = new BooleanHolder(false); if (!this.selfTarget && stages.value < 0) { - // TODO: Include simulate boolean when tag applications can be simulated - this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled); + this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, cancelled); } if (!cancelled.value && !this.selfTarget && stages.value < 0) { diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 115e4f640a2..58683cf8ec8 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -196,7 +196,7 @@ export class TitlePhase extends Phase { this.scene.gameMode = getGameMode(GameModes.DAILY); this.scene.setSeed(seed); - this.scene.resetSeed(1); + this.scene.resetSeed(0); this.scene.money = this.scene.gameMode.getStartingMoney(); diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 95d55986185..497d449912f 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -13,10 +13,10 @@ import { BerryPhase } from "./berry-phase"; import { FieldPhase } from "./field-phase"; import { MoveHeaderPhase } from "./move-header-phase"; import { MovePhase } from "./move-phase"; -import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase"; import { SwitchSummonPhase } from "./switch-summon-phase"; import { TurnEndPhase } from "./turn-end-phase"; import { WeatherEffectPhase } from "./weather-effect-phase"; +import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase"; import { BattlerIndex } from "#app/battle"; import { TrickRoomTag } from "#app/data/arena-tag"; import { SwitchType } from "#enums/switch-type"; @@ -45,7 +45,7 @@ export class TurnStartPhase extends FieldPhase { // Next, a check for Trick Room is applied to determine sort order. const speedReversed = new Utils.BooleanHolder(false); - this.scene.arena.applyTags(TrickRoomTag, speedReversed); + this.scene.arena.applyTags(TrickRoomTag, false, speedReversed); // Adjust the sort function based on whether Trick Room is active. orderedTargets.sort((a: Pokemon, b: Pokemon) => { @@ -152,65 +152,62 @@ export class TurnStartPhase extends FieldPhase { } switch (turnCommand?.command) { - case Command.FIGHT: - const queuedMove = turnCommand.move; - pokemon.turnData.order = orderIndex++; - if (!queuedMove) { - continue; - } - const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); - if (move.getMove().hasAttr(MoveHeaderAttr)) { - this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); - } - if (pokemon.isPlayer()) { - if (turnCommand.cursor === -1) { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? - } else { - const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? - this.scene.pushPhase(playerPhase); + case Command.FIGHT: + const queuedMove = turnCommand.move; + pokemon.turnData.order = orderIndex++; + if (!queuedMove) { + continue; } - } else { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? - } - break; - case Command.BALL: - this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? - break; - case Command.POKEMON: - const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH; - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer())); - break; - case Command.RUN: - let runningPokemon = pokemon; - if (this.scene.currentBattle.double) { - const playerActivePokemon = field.filter(pokemon => { - if (!!pokemon) { - return pokemon.isPlayer() && pokemon.isActive(); + const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move); + if (move.getMove().hasAttr(MoveHeaderAttr)) { + this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); + } + if (pokemon.isPlayer()) { + if (turnCommand.cursor === -1) { + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? } else { - return; + const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? + this.scene.pushPhase(playerPhase); } - }); - // if only one pokemon is alive, use that one - if (playerActivePokemon.length > 1) { - // find which active pokemon has faster speed - const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1]; - // check if either active pokemon has the ability "Run Away" - const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY)); - runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; + } else { + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? } - } - this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); - break; + break; + case Command.BALL: + this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? + break; + case Command.POKEMON: + const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH; + this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer())); + break; + case Command.RUN: + let runningPokemon = pokemon; + if (this.scene.currentBattle.double) { + const playerActivePokemon = field.filter(pokemon => { + if (!!pokemon) { + return pokemon.isPlayer() && pokemon.isActive(); + } else { + return; + } + }); + // if only one pokemon is alive, use that one + if (playerActivePokemon.length > 1) { + // find which active pokemon has faster speed + const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1]; + // check if either active pokemon has the ability "Run Away" + const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY)); + runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; + } + } + this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); + break; } } this.scene.pushPhase(new WeatherEffectPhase(this.scene)); - for (const o of moveOrder) { - if (field[o].status && field[o].status.isPostTurn()) { - this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o)); - } - } + /** Add a new phase to check who should be taking status damage */ + this.scene.pushPhase(new CheckStatusEffectPhase(this.scene, moveOrder)); this.scene.pushPhase(new BerryPhase(this.scene)); this.scene.pushPhase(new TurnEndPhase(this.scene)); diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index be4c6983c0a..d24484bbf9d 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -81,6 +81,8 @@ const namespaceMap = { miscDialogue: "dialogue-misc", battleSpecDialogue: "dialogue-final-boss", doubleBattleDialogue: "dialogue-double-battle", + splashMessages: "splash-texts", + mysteryEncounterMessages: "mystery-encounter-texts", }; //#region Functions diff --git a/src/system/achv.ts b/src/system/achv.ts index 7bac631d7aa..366813328e2 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -109,7 +109,7 @@ export class DamageAchv extends Achv { damageAmount: integer; constructor(localizationKey: string, name: string, damageAmount: integer, iconImage: string, score: integer) { - super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] as Utils.NumberHolder).value >= this.damageAmount); + super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.damageAmount); this.damageAmount = damageAmount; } } @@ -118,7 +118,7 @@ export class HealAchv extends Achv { healAmount: integer; constructor(localizationKey: string, name: string, healAmount: integer, iconImage: string, score: integer) { - super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] as Utils.NumberHolder).value >= this.healAmount); + super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.healAmount); this.healAmount = healAmount; } } @@ -127,7 +127,7 @@ export class LevelAchv extends Achv { level: integer; constructor(localizationKey: string, name: string, level: integer, iconImage: string, score: integer) { - super(localizationKey, name, "", iconImage, score, (scene: BattleScene, args: any[]) => (args[0] as Utils.IntegerHolder).value >= this.level); + super(localizationKey, name, "", iconImage, score, (scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.level); this.level = level; } } @@ -156,133 +156,133 @@ export function getAchievementDescription(localizationKey: string): string { const genderStr = PlayerGender[genderIndex].toLowerCase(); switch (localizationKey) { - case "10K_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10K_MONEY.moneyAmount.toLocaleString("en-US") }); - case "100K_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._100K_MONEY.moneyAmount.toLocaleString("en-US") }); - case "1M_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._1M_MONEY.moneyAmount.toLocaleString("en-US") }); - case "10M_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10M_MONEY.moneyAmount.toLocaleString("en-US") }); - case "250_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._250_DMG.damageAmount.toLocaleString("en-US") }); - case "1000_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._1000_DMG.damageAmount.toLocaleString("en-US") }); - case "2500_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._2500_DMG.damageAmount.toLocaleString("en-US") }); - case "10000_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US") }); - case "250_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "1000_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "2500_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "10000_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "LV_100": - return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_100.level }); - case "LV_250": - return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_250.level }); - case "LV_1000": - return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_1000.level }); - case "10_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._10_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "25_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._25_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "50_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._50_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "75_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "100_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "TRANSFER_MAX_STAT_STAGE": - return i18next.t("achv:TRANSFER_MAX_STAT_STAGE.description", { context: genderStr }); - case "MAX_FRIENDSHIP": - return i18next.t("achv:MAX_FRIENDSHIP.description", { context: genderStr }); - case "MEGA_EVOLVE": - return i18next.t("achv:MEGA_EVOLVE.description", { context: genderStr }); - case "GIGANTAMAX": - return i18next.t("achv:GIGANTAMAX.description", { context: genderStr }); - case "TERASTALLIZE": - return i18next.t("achv:TERASTALLIZE.description", { context: genderStr }); - case "STELLAR_TERASTALLIZE": - return i18next.t("achv:STELLAR_TERASTALLIZE.description", { context: genderStr }); - case "SPLICE": - return i18next.t("achv:SPLICE.description", { context: genderStr }); - case "MINI_BLACK_HOLE": - return i18next.t("achv:MINI_BLACK_HOLE.description", { context: genderStr }); - case "CATCH_MYTHICAL": - return i18next.t("achv:CATCH_MYTHICAL.description", { context: genderStr }); - case "CATCH_SUB_LEGENDARY": - return i18next.t("achv:CATCH_SUB_LEGENDARY.description", { context: genderStr }); - case "CATCH_LEGENDARY": - return i18next.t("achv:CATCH_LEGENDARY.description", { context: genderStr }); - case "SEE_SHINY": - return i18next.t("achv:SEE_SHINY.description", { context: genderStr }); - case "SHINY_PARTY": - return i18next.t("achv:SHINY_PARTY.description", { context: genderStr }); - case "HATCH_MYTHICAL": - return i18next.t("achv:HATCH_MYTHICAL.description", { context: genderStr }); - case "HATCH_SUB_LEGENDARY": - return i18next.t("achv:HATCH_SUB_LEGENDARY.description", { context: genderStr }); - case "HATCH_LEGENDARY": - return i18next.t("achv:HATCH_LEGENDARY.description", { context: genderStr }); - case "HATCH_SHINY": - return i18next.t("achv:HATCH_SHINY.description", { context: genderStr }); - case "HIDDEN_ABILITY": - return i18next.t("achv:HIDDEN_ABILITY.description", { context: genderStr }); - case "PERFECT_IVS": - return i18next.t("achv:PERFECT_IVS.description", { context: genderStr }); - case "CLASSIC_VICTORY": - return i18next.t("achv:CLASSIC_VICTORY.description", { context: genderStr }); - case "UNEVOLVED_CLASSIC_VICTORY": - return i18next.t("achv:UNEVOLVED_CLASSIC_VICTORY.description", { context: genderStr }); - case "MONO_GEN_ONE": - return i18next.t("achv:MONO_GEN_ONE.description", { context: genderStr }); - case "MONO_GEN_TWO": - return i18next.t("achv:MONO_GEN_TWO.description", { context: genderStr }); - case "MONO_GEN_THREE": - return i18next.t("achv:MONO_GEN_THREE.description", { context: genderStr }); - case "MONO_GEN_FOUR": - return i18next.t("achv:MONO_GEN_FOUR.description", { context: genderStr }); - case "MONO_GEN_FIVE": - return i18next.t("achv:MONO_GEN_FIVE.description", { context: genderStr }); - case "MONO_GEN_SIX": - return i18next.t("achv:MONO_GEN_SIX.description", { context: genderStr }); - case "MONO_GEN_SEVEN": - return i18next.t("achv:MONO_GEN_SEVEN.description", { context: genderStr }); - case "MONO_GEN_EIGHT": - return i18next.t("achv:MONO_GEN_EIGHT.description", { context: genderStr }); - case "MONO_GEN_NINE": - return i18next.t("achv:MONO_GEN_NINE.description", { context: genderStr }); - case "MONO_NORMAL": - case "MONO_FIGHTING": - case "MONO_FLYING": - case "MONO_POISON": - case "MONO_GROUND": - case "MONO_ROCK": - case "MONO_BUG": - case "MONO_GHOST": - case "MONO_STEEL": - case "MONO_FIRE": - case "MONO_WATER": - case "MONO_GRASS": - case "MONO_ELECTRIC": - case "MONO_PSYCHIC": - case "MONO_ICE": - case "MONO_DRAGON": - case "MONO_DARK": - case "MONO_FAIRY": - return i18next.t("achv:MonoType.description", { context: genderStr, "type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`) }); - case "FRESH_START": - return i18next.t("achv:FRESH_START.description", { context: genderStr }); - case "INVERSE_BATTLE": - return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr }); - case "BREEDERS_IN_SPACE": - return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr }); - default: - return ""; + case "10K_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10K_MONEY.moneyAmount.toLocaleString("en-US") }); + case "100K_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._100K_MONEY.moneyAmount.toLocaleString("en-US") }); + case "1M_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._1M_MONEY.moneyAmount.toLocaleString("en-US") }); + case "10M_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10M_MONEY.moneyAmount.toLocaleString("en-US") }); + case "250_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._250_DMG.damageAmount.toLocaleString("en-US") }); + case "1000_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._1000_DMG.damageAmount.toLocaleString("en-US") }); + case "2500_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._2500_DMG.damageAmount.toLocaleString("en-US") }); + case "10000_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US") }); + case "250_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "1000_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "2500_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "10000_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "LV_100": + return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_100.level }); + case "LV_250": + return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_250.level }); + case "LV_1000": + return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_1000.level }); + case "10_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._10_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "25_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._25_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "50_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._50_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "75_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "100_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "TRANSFER_MAX_STAT_STAGE": + return i18next.t("achv:TRANSFER_MAX_STAT_STAGE.description", { context: genderStr }); + case "MAX_FRIENDSHIP": + return i18next.t("achv:MAX_FRIENDSHIP.description", { context: genderStr }); + case "MEGA_EVOLVE": + return i18next.t("achv:MEGA_EVOLVE.description", { context: genderStr }); + case "GIGANTAMAX": + return i18next.t("achv:GIGANTAMAX.description", { context: genderStr }); + case "TERASTALLIZE": + return i18next.t("achv:TERASTALLIZE.description", { context: genderStr }); + case "STELLAR_TERASTALLIZE": + return i18next.t("achv:STELLAR_TERASTALLIZE.description", { context: genderStr }); + case "SPLICE": + return i18next.t("achv:SPLICE.description", { context: genderStr }); + case "MINI_BLACK_HOLE": + return i18next.t("achv:MINI_BLACK_HOLE.description", { context: genderStr }); + case "CATCH_MYTHICAL": + return i18next.t("achv:CATCH_MYTHICAL.description", { context: genderStr }); + case "CATCH_SUB_LEGENDARY": + return i18next.t("achv:CATCH_SUB_LEGENDARY.description", { context: genderStr }); + case "CATCH_LEGENDARY": + return i18next.t("achv:CATCH_LEGENDARY.description", { context: genderStr }); + case "SEE_SHINY": + return i18next.t("achv:SEE_SHINY.description", { context: genderStr }); + case "SHINY_PARTY": + return i18next.t("achv:SHINY_PARTY.description", { context: genderStr }); + case "HATCH_MYTHICAL": + return i18next.t("achv:HATCH_MYTHICAL.description", { context: genderStr }); + case "HATCH_SUB_LEGENDARY": + return i18next.t("achv:HATCH_SUB_LEGENDARY.description", { context: genderStr }); + case "HATCH_LEGENDARY": + return i18next.t("achv:HATCH_LEGENDARY.description", { context: genderStr }); + case "HATCH_SHINY": + return i18next.t("achv:HATCH_SHINY.description", { context: genderStr }); + case "HIDDEN_ABILITY": + return i18next.t("achv:HIDDEN_ABILITY.description", { context: genderStr }); + case "PERFECT_IVS": + return i18next.t("achv:PERFECT_IVS.description", { context: genderStr }); + case "CLASSIC_VICTORY": + return i18next.t("achv:CLASSIC_VICTORY.description", { context: genderStr }); + case "UNEVOLVED_CLASSIC_VICTORY": + return i18next.t("achv:UNEVOLVED_CLASSIC_VICTORY.description", { context: genderStr }); + case "MONO_GEN_ONE": + return i18next.t("achv:MONO_GEN_ONE.description", { context: genderStr }); + case "MONO_GEN_TWO": + return i18next.t("achv:MONO_GEN_TWO.description", { context: genderStr }); + case "MONO_GEN_THREE": + return i18next.t("achv:MONO_GEN_THREE.description", { context: genderStr }); + case "MONO_GEN_FOUR": + return i18next.t("achv:MONO_GEN_FOUR.description", { context: genderStr }); + case "MONO_GEN_FIVE": + return i18next.t("achv:MONO_GEN_FIVE.description", { context: genderStr }); + case "MONO_GEN_SIX": + return i18next.t("achv:MONO_GEN_SIX.description", { context: genderStr }); + case "MONO_GEN_SEVEN": + return i18next.t("achv:MONO_GEN_SEVEN.description", { context: genderStr }); + case "MONO_GEN_EIGHT": + return i18next.t("achv:MONO_GEN_EIGHT.description", { context: genderStr }); + case "MONO_GEN_NINE": + return i18next.t("achv:MONO_GEN_NINE.description", { context: genderStr }); + case "MONO_NORMAL": + case "MONO_FIGHTING": + case "MONO_FLYING": + case "MONO_POISON": + case "MONO_GROUND": + case "MONO_ROCK": + case "MONO_BUG": + case "MONO_GHOST": + case "MONO_STEEL": + case "MONO_FIRE": + case "MONO_WATER": + case "MONO_GRASS": + case "MONO_ELECTRIC": + case "MONO_PSYCHIC": + case "MONO_ICE": + case "MONO_DRAGON": + case "MONO_DARK": + case "MONO_FAIRY": + return i18next.t("achv:MonoType.description", { context: genderStr, "type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`) }); + case "FRESH_START": + return i18next.t("achv:FRESH_START.description", { context: genderStr }); + case "INVERSE_BATTLE": + return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr }); + case "BREEDERS_IN_SPACE": + return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr }); + default: + return ""; } } diff --git a/src/system/arena-data.ts b/src/system/arena-data.ts index 5b907805372..ba37de0ed0e 100644 --- a/src/system/arena-data.ts +++ b/src/system/arena-data.ts @@ -1,5 +1,5 @@ import { Arena } from "../field/arena"; -import { ArenaTag } from "../data/arena-tag"; +import { ArenaTag, loadArenaTag } from "../data/arena-tag"; import { Biome } from "#enums/biome"; import { Weather } from "../data/weather"; import { Terrain } from "#app/data/terrain"; @@ -15,6 +15,10 @@ export default class ArenaData { this.biome = sourceArena ? sourceArena.biomeType : source.biome; this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null; this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null; - this.tags = sourceArena ? sourceArena.tags : []; + this.tags = []; + + if (source.tags) { + this.tags = source.tags.map(t => loadArenaTag(t)); + } } } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index eada3d270ef..5f9aad63408 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -31,7 +31,7 @@ import { TrainerVariant } from "#app/field/trainer"; import { Variant } from "#app/data/variant"; import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad"; import { setSettingKeyboard, SettingKeyboard } from "#app/system/settings/settings-keyboard"; -import { TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; +import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; import * as Modifier from "#app/modifier/modifier"; import { StatusEffect } from "#app/data/status-effect"; import ChallengeData from "#app/system/challenge-data"; @@ -43,13 +43,13 @@ import { Species } from "#enums/species"; import { applyChallenges, ChallengeType } from "#app/data/challenge"; import { WeatherType } from "#enums/weather-type"; import { TerrainType } from "#app/data/terrain"; -import { OutdatedPhase } from "#app/phases/outdated-phase"; import { ReloadSessionPhase } from "#app/phases/reload-session-phase"; import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler"; -import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "#app/system/version-converter"; +import { applySessionVersionMigration, applySystemVersionMigration, applySettingsVersionMigration } from "./version_migration/version_converter"; import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api"; +import { ArenaTrapTag } from "#app/data/arena-tag"; export const defaultStarterSpecies: Species[] = [ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, @@ -67,22 +67,22 @@ const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet n export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): string { switch (dataType) { - case GameDataType.SYSTEM: - return "data"; - case GameDataType.SESSION: - let ret = "sessionData"; - if (slotId) { - ret += slotId; - } - return ret; - case GameDataType.SETTINGS: - return "settings"; - case GameDataType.TUTORIALS: - return "tutorials"; - case GameDataType.SEEN_DIALOGUES: - return "seenDialogues"; - case GameDataType.RUN_HISTORY: - return "runHistoryData"; + case GameDataType.SYSTEM: + return "data"; + case GameDataType.SESSION: + let ret = "sessionData"; + if (slotId) { + ret += slotId; + } + return ret; + case GameDataType.SETTINGS: + return "settings"; + case GameDataType.TUTORIALS: + return "tutorials"; + case GameDataType.SEEN_DIALOGUES: + return "seenDialogues"; + case GameDataType.RUN_HISTORY: + return "runHistoryData"; } } @@ -402,10 +402,7 @@ export class GameData { .then(error => { this.scene.ui.savingIcon.hide(); if (error) { - if (error.startsWith("client version out of date")) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new OutdatedPhase(this.scene)); - } else if (error.startsWith("session out of date")) { + if (error.startsWith("session out of date")) { this.scene.clearPhaseQueue(); this.scene.unshiftPhase(new ReloadSessionPhase(this.scene)); } @@ -481,7 +478,7 @@ export class GameData { localStorage.setItem(lsItemKey, ""); } - applySystemDataPatches(systemData); + applySystemVersionMigration(systemData); this.trainerId = systemData.trainerId; this.secretId = systemData.secretId; @@ -856,7 +853,7 @@ export class GameData { const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct? - applySettingsDataPatches(settings); + applySettingsVersionMigration(settings); for (const setting of Object.keys(settings)) { setSetting(this.scene, setting, settings[setting]); @@ -1085,8 +1082,18 @@ export class GameData { scene.arena.terrain = sessionData.arena.terrain; scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, scene.arena.terrain?.terrainType!, scene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct? - // TODO - //scene.arena.tags = sessionData.arena.tags; + + scene.arena.tags = sessionData.arena.tags; + if (scene.arena.tags) { + for (const tag of scene.arena.tags) { + if (tag instanceof ArenaTrapTag) { + const { tagType, side, turnCount, layers, maxLayers } = tag as ArenaTrapTag; + scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers)); + } else { + scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tag.tagType, tag.side, tag.turnCount)); + } + } + } for (const modifierData of sessionData.modifiers) { const modifier = modifierData.toModifier(scene, Modifier[modifierData.className]); @@ -1125,10 +1132,16 @@ export class GameData { }); } + /** + * Delete the session data at the given slot when overwriting a save file + * For deleting the session of a finished run, use {@linkcode tryClearSession} + * @param slotId the slot to clear + * @returns Promise with result `true` if the session was deleted successfully, `false` otherwise + */ deleteSession(slotId: integer): Promise { return new Promise(resolve => { if (bypassLogin) { - localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`); + localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); return resolve(true); } @@ -1139,7 +1152,7 @@ export class GameData { Utils.apiFetch(`savedata/session/delete?slot=${slotId}&clientSessionId=${clientSessionId}`, true).then(response => { if (response.ok) { loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct? - localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`); + localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); resolve(true); } return response.text(); @@ -1190,7 +1203,9 @@ export class GameData { /** - * Attempt to clear session data. After session data is removed, attempt to update user info so the menu updates + * Attempt to clear session data after the end of a run + * After session data is removed, attempt to update user info so the menu updates + * To delete an unfinished run instead, use {@linkcode deleteSession} */ async tryClearSession(scene: BattleScene, slotId: integer): Promise<[success: boolean, newClear: boolean]> { let result: [boolean, boolean] = [ false, false ]; @@ -1204,7 +1219,7 @@ export class GameData { if (response.ok) { loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct? - localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`); + localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); } const jsonResponse: PokerogueApiClearSessionData = await response.json(); @@ -1294,7 +1309,7 @@ export class GameData { return v; }) as SessionSaveData; - applySessionDataPatches(sessionData); + applySessionVersionMigration(sessionData); return sessionData; } @@ -1335,10 +1350,7 @@ export class GameData { this.scene.ui.savingIcon.hide(); } if (error) { - if (error.startsWith("client version out of date")) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new OutdatedPhase(this.scene)); - } else if (error.startsWith("session out of date")) { + if (error.startsWith("session out of date")) { this.scene.clearPhaseQueue(); this.scene.unshiftPhase(new ReloadSessionPhase(this.scene)); } @@ -1362,9 +1374,9 @@ export class GameData { const dataKey: string = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`; const handleData = (dataStr: string) => { switch (dataType) { - case GameDataType.SYSTEM: - dataStr = this.convertSystemDataStr(dataStr, true); - break; + case GameDataType.SYSTEM: + dataStr = this.convertSystemDataStr(dataStr, true); + break; } const encryptedData = AES.encrypt(dataStr, saveKey); const blob = new Blob([ encryptedData.toString() ], { type: "text/json" }); @@ -1422,28 +1434,28 @@ export class GameData { try { dataName = GameDataType[dataType].toLowerCase(); switch (dataType) { - case GameDataType.SYSTEM: - dataStr = this.convertSystemDataStr(dataStr); - const systemData = this.parseSystemData(dataStr); - valid = !!systemData.dexData && !!systemData.timestamp; - break; - case GameDataType.SESSION: - const sessionData = this.parseSessionData(dataStr); - valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp; - break; - case GameDataType.RUN_HISTORY: - const data = JSON.parse(dataStr); - const keys = Object.keys(data); - dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase(); - keys.forEach((key) => { - const entryKeys = Object.keys(data[key]); - valid = [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3; - }); - break; - case GameDataType.SETTINGS: - case GameDataType.TUTORIALS: - valid = true; - break; + case GameDataType.SYSTEM: + dataStr = this.convertSystemDataStr(dataStr); + const systemData = this.parseSystemData(dataStr); + valid = !!systemData.dexData && !!systemData.timestamp; + break; + case GameDataType.SESSION: + const sessionData = this.parseSessionData(dataStr); + valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp; + break; + case GameDataType.RUN_HISTORY: + const data = JSON.parse(dataStr); + const keys = Object.keys(data); + dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase(); + keys.forEach((key) => { + const entryKeys = Object.keys(data[key]); + valid = [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3; + }); + break; + case GameDataType.SETTINGS: + case GameDataType.TUTORIALS: + valid = true; + break; } } catch (ex) { console.error(ex); diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 8240b6bcf84..cddc5798872 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -12,7 +12,7 @@ import { loadBattlerTag } from "../data/battler-tags"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; export default class PokemonData { public id: integer; @@ -33,7 +33,6 @@ export default class PokemonData { public stats: integer[]; public ivs: integer[]; public nature: Nature; - public natureOverride: Nature | -1; public moveset: (PokemonMove | null)[]; public status: Status | null; public friendship: integer; @@ -54,14 +53,20 @@ export default class PokemonData { public fusionVariant: Variant; public fusionGender: Gender; public fusionLuck: integer; - public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData; public boss: boolean; public bossSegments?: integer; public summonData: PokemonSummonData; + /** Data that can customize a Pokemon in non-standard ways from its Species */ - public mysteryEncounterPokemonData: MysteryEncounterPokemonData; + public customPokemonData: CustomPokemonData; + public fusionCustomPokemonData: CustomPokemonData; + + // Deprecated attributes, needed for now to allow SessionData migration (see PR#4619 comments) + public natureOverride: Nature | -1; + public mysteryEncounterPokemonData: CustomPokemonData | null; + public fusionMysteryEncounterPokemonData: CustomPokemonData | null; constructor(source: Pokemon | any, forHistory: boolean = false) { const sourcePokemon = source instanceof Pokemon ? source : null; @@ -107,9 +112,13 @@ export default class PokemonData { this.fusionVariant = source.fusionVariant; this.fusionGender = source.fusionGender; this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0); + this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData); this.usedTMs = source.usedTMs ?? []; - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(source.mysteryEncounterPokemonData); + this.customPokemonData = new CustomPokemonData(source.customPokemonData); + + this.mysteryEncounterPokemonData = new CustomPokemonData(source.mysteryEncounterPokemonData); + this.fusionMysteryEncounterPokemonData = new CustomPokemonData(source.fusionMysteryEncounterPokemonData); if (!forHistory) { this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss); diff --git a/src/system/settings/settings-gamepad.ts b/src/system/settings/settings-gamepad.ts index c96ec36f9a5..322b2baac9e 100644 --- a/src/system/settings/settings-gamepad.ts +++ b/src/system/settings/settings-gamepad.ts @@ -82,66 +82,66 @@ export const settingGamepadBlackList = [ export function setSettingGamepad(scene: BattleScene, setting: SettingGamepad, value: integer): boolean { switch (setting) { - case SettingGamepad.Gamepad_Support: + case SettingGamepad.Gamepad_Support: // if we change the value of the gamepad support, we call a method in the inputController to // activate or deactivate the controller listener - scene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== "Disabled"); - break; - case SettingGamepad.Button_Action: - case SettingGamepad.Button_Cancel: - case SettingGamepad.Button_Menu: - case SettingGamepad.Button_Stats: - case SettingGamepad.Button_Cycle_Shiny: - case SettingGamepad.Button_Cycle_Form: - case SettingGamepad.Button_Cycle_Gender: - case SettingGamepad.Button_Cycle_Ability: - case SettingGamepad.Button_Cycle_Nature: - case SettingGamepad.Button_Cycle_Variant: - case SettingGamepad.Button_Speed_Up: - case SettingGamepad.Button_Slow_Down: - case SettingGamepad.Button_Submit: - if (value) { - if (scene.ui) { - const cancelHandler = (success: boolean = false) : boolean => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); - return success; - }; - scene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, { - target: setting, - cancelHandler: cancelHandler, - }); + scene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== "Disabled"); + break; + case SettingGamepad.Button_Action: + case SettingGamepad.Button_Cancel: + case SettingGamepad.Button_Menu: + case SettingGamepad.Button_Stats: + case SettingGamepad.Button_Cycle_Shiny: + case SettingGamepad.Button_Cycle_Form: + case SettingGamepad.Button_Cycle_Gender: + case SettingGamepad.Button_Cycle_Ability: + case SettingGamepad.Button_Cycle_Nature: + case SettingGamepad.Button_Cycle_Variant: + case SettingGamepad.Button_Speed_Up: + case SettingGamepad.Button_Slow_Down: + case SettingGamepad.Button_Submit: + if (value) { + if (scene.ui) { + const cancelHandler = (success: boolean = false) : boolean => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + return success; + }; + scene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, { + target: setting, + cancelHandler: cancelHandler, + }); + } } - } - break; - case SettingGamepad.Controller: - if (value) { - const gp = scene.inputController.getGamepadsName(); - if (scene.ui && gp) { - const cancelHandler = () => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Controller), 0, true); - (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + break; + case SettingGamepad.Controller: + if (value) { + const gp = scene.inputController.getGamepadsName(); + if (scene.ui && gp) { + const cancelHandler = () => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Controller), 0, true); + (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + return false; + }; + const changeGamepadHandler = (gamepad: string) => { + scene.inputController.setChosenGamepad(gamepad); + cancelHandler(); + return true; + }; + scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + options: [ ...gp.map((g: string) => ({ + label: truncateString(g, 30), // Truncate the gamepad name for display + handler: () => changeGamepadHandler(g) + })), { + label: "Cancel", + handler: cancelHandler, + }] + }); return false; - }; - const changeGamepadHandler = (gamepad: string) => { - scene.inputController.setChosenGamepad(gamepad); - cancelHandler(); - return true; - }; - scene.ui.setOverlayMode(Mode.OPTION_SELECT, { - options: [ ...gp.map((g: string) => ({ - label: truncateString(g, 30), // Truncate the gamepad name for display - handler: () => changeGamepadHandler(g) - })), { - label: "Cancel", - handler: cancelHandler, - }] - }); - return false; + } } - } - break; + break; } return true; diff --git a/src/system/settings/settings-keyboard.ts b/src/system/settings/settings-keyboard.ts index d7cb994aca8..97990f61c86 100644 --- a/src/system/settings/settings-keyboard.ts +++ b/src/system/settings/settings-keyboard.ts @@ -135,53 +135,53 @@ export const settingKeyboardBlackList = [ export function setSettingKeyboard(scene: BattleScene, setting: SettingKeyboard, value: integer): boolean { switch (setting) { - case SettingKeyboard.Button_Up: - case SettingKeyboard.Button_Down: - case SettingKeyboard.Button_Left: - case SettingKeyboard.Button_Right: - case SettingKeyboard.Button_Action: - case SettingKeyboard.Button_Cancel: - case SettingKeyboard.Button_Menu: - case SettingKeyboard.Button_Stats: - case SettingKeyboard.Button_Cycle_Shiny: - case SettingKeyboard.Button_Cycle_Form: - case SettingKeyboard.Button_Cycle_Gender: - case SettingKeyboard.Button_Cycle_Ability: - case SettingKeyboard.Button_Cycle_Nature: - case SettingKeyboard.Button_Cycle_Variant: - case SettingKeyboard.Button_Speed_Up: - case SettingKeyboard.Button_Slow_Down: - case SettingKeyboard.Alt_Button_Up: - case SettingKeyboard.Alt_Button_Down: - case SettingKeyboard.Alt_Button_Left: - case SettingKeyboard.Alt_Button_Right: - case SettingKeyboard.Alt_Button_Action: - case SettingKeyboard.Alt_Button_Cancel: - case SettingKeyboard.Alt_Button_Menu: - case SettingKeyboard.Alt_Button_Stats: - case SettingKeyboard.Alt_Button_Cycle_Shiny: - case SettingKeyboard.Alt_Button_Cycle_Form: - case SettingKeyboard.Alt_Button_Cycle_Gender: - case SettingKeyboard.Alt_Button_Cycle_Ability: - case SettingKeyboard.Alt_Button_Cycle_Nature: - case SettingKeyboard.Alt_Button_Cycle_Variant: - case SettingKeyboard.Alt_Button_Speed_Up: - case SettingKeyboard.Alt_Button_Slow_Down: - case SettingKeyboard.Alt_Button_Submit: - if (value) { - if (scene.ui) { - const cancelHandler = (success: boolean = false) : boolean => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); - return success; - }; - scene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, { - target: setting, - cancelHandler: cancelHandler, - }); + case SettingKeyboard.Button_Up: + case SettingKeyboard.Button_Down: + case SettingKeyboard.Button_Left: + case SettingKeyboard.Button_Right: + case SettingKeyboard.Button_Action: + case SettingKeyboard.Button_Cancel: + case SettingKeyboard.Button_Menu: + case SettingKeyboard.Button_Stats: + case SettingKeyboard.Button_Cycle_Shiny: + case SettingKeyboard.Button_Cycle_Form: + case SettingKeyboard.Button_Cycle_Gender: + case SettingKeyboard.Button_Cycle_Ability: + case SettingKeyboard.Button_Cycle_Nature: + case SettingKeyboard.Button_Cycle_Variant: + case SettingKeyboard.Button_Speed_Up: + case SettingKeyboard.Button_Slow_Down: + case SettingKeyboard.Alt_Button_Up: + case SettingKeyboard.Alt_Button_Down: + case SettingKeyboard.Alt_Button_Left: + case SettingKeyboard.Alt_Button_Right: + case SettingKeyboard.Alt_Button_Action: + case SettingKeyboard.Alt_Button_Cancel: + case SettingKeyboard.Alt_Button_Menu: + case SettingKeyboard.Alt_Button_Stats: + case SettingKeyboard.Alt_Button_Cycle_Shiny: + case SettingKeyboard.Alt_Button_Cycle_Form: + case SettingKeyboard.Alt_Button_Cycle_Gender: + case SettingKeyboard.Alt_Button_Cycle_Ability: + case SettingKeyboard.Alt_Button_Cycle_Nature: + case SettingKeyboard.Alt_Button_Cycle_Variant: + case SettingKeyboard.Alt_Button_Speed_Up: + case SettingKeyboard.Alt_Button_Slow_Down: + case SettingKeyboard.Alt_Button_Submit: + if (value) { + if (scene.ui) { + const cancelHandler = (success: boolean = false) : boolean => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); + return success; + }; + scene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, { + target: setting, + cancelHandler: cancelHandler, + }); + } } - } - break; + break; // case SettingKeyboard.Default_Layout: // if (value && scene.ui) { // const cancelHandler = () => { diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 66021845c29..be440d5d93e 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -76,16 +76,16 @@ const SHOP_CURSOR_TARGET_OPTIONS: SettingOption[] = [ const shopCursorTargetIndexMap = SHOP_CURSOR_TARGET_OPTIONS.map(option => { switch (option.value) { - case "Rewards": - return ShopCursorTarget.REWARDS; - case "Shop": - return ShopCursorTarget.SHOP; - case "Reroll": - return ShopCursorTarget.REROLL; - case "Check Team": - return ShopCursorTarget.CHECK_TEAM; - default: - throw new Error(`Unknown value: ${option.value}`); + case "Rewards": + return ShopCursorTarget.REWARDS; + case "Shop": + return ShopCursorTarget.SHOP; + case "Reroll": + return ShopCursorTarget.REROLL; + case "Check Team": + return ShopCursorTarget.CHECK_TEAM; + default: + throw new Error(`Unknown value: ${option.value}`); } }); @@ -699,226 +699,226 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): return false; } switch (Setting[index].key) { - case SettingKeys.Game_Speed: - scene.gameSpeed = parseFloat(Setting[index].options[value].value.replace("x", "")); - break; - case SettingKeys.Master_Volume: - scene.masterVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.BGM_Volume: - scene.bgmVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.Field_Volume: - scene.fieldVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.SE_Volume: - scene.seVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.UI_Volume: - scene.uiVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - break; - case SettingKeys.Music_Preference: - scene.musicPreference = value; - break; - case SettingKeys.Damage_Numbers: - scene.damageNumbersMode = value; - break; - case SettingKeys.UI_Theme: - scene.uiTheme = value; - break; - case SettingKeys.Window_Type: - updateWindowType(scene, parseInt(Setting[index].options[value].value)); - break; - case SettingKeys.Tutorials: - scene.enableTutorials = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Move_Info: - scene.enableMoveInfo = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Enable_Retries: - scene.enableRetries = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Hide_IVs: - scene.hideIvs = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Skip_Seen_Dialogues: - scene.skipSeenDialogues = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Egg_Skip: - scene.eggSkipPreference = value; - break; - case SettingKeys.Battle_Style: - scene.battleStyle = value; - break; - case SettingKeys.Show_BGM_Bar: - scene.showBgmBar = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Candy_Upgrade_Notification: - if (scene.candyUpgradeNotification === value) { + case SettingKeys.Game_Speed: + scene.gameSpeed = parseFloat(Setting[index].options[value].value.replace("x", "")); break; - } - scene.candyUpgradeNotification = value; - scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); - break; - case SettingKeys.Candy_Upgrade_Display: - scene.candyUpgradeDisplay = value; - case SettingKeys.Money_Format: - switch (Setting[index].options[value].value) { - case "Normal": - scene.moneyFormat = MoneyFormat.NORMAL; + case SettingKeys.Master_Volume: + scene.masterVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); break; - case "Abbreviated": - scene.moneyFormat = MoneyFormat.ABBREVIATED; + case SettingKeys.BGM_Volume: + scene.bgmVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); break; - } - scene.updateMoneyText(false); - break; - case SettingKeys.Sprite_Set: - scene.experimentalSprites = !!value; - if (value) { - scene.initExpSprites(); - } - break; - case SettingKeys.Move_Animations: - scene.moveAnimations = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Show_Moveset_Flyout: - scene.showMovesetFlyout = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Show_Arena_Flyout: - scene.showArenaFlyout = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Show_Time_Of_Day_Widget: - scene.showTimeOfDayWidget = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Time_Of_Day_Animation: - scene.timeOfDayAnimation = Setting[index].options[value].value === "Bounce" ? EaseType.BOUNCE : EaseType.BACK; - break; - case SettingKeys.Show_Stats_on_Level_Up: - scene.showLevelUpStats = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Shop_Cursor_Target: - const selectedValue = shopCursorTargetIndexMap[value]; - scene.shopCursorTarget = selectedValue; - break; - case SettingKeys.EXP_Gains_Speed: - scene.expGainsSpeed = value; - break; - case SettingKeys.EXP_Party_Display: - scene.expParty = value; - break; - case SettingKeys.HP_Bar_Speed: - scene.hpBarSpeed = value; - break; - case SettingKeys.Fusion_Palette_Swaps: - scene.fusionPaletteSwaps = !!value; - break; - case SettingKeys.Player_Gender: - if (scene.gameData) { - const female = Setting[index].options[value].value === "Girl"; - scene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE; - scene.trainer.setTexture(scene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m")); - } else { - return false; - } - break; - case SettingKeys.Touch_Controls: - scene.enableTouchControls = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); - const touchControls = document.getElementById("touchControls"); - if (touchControls) { - touchControls.classList.toggle("visible", scene.enableTouchControls); - } - break; - case SettingKeys.Vibration: - scene.enableVibration = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); - break; - case SettingKeys.Type_Hints: - scene.typeHints = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Language: - if (value) { - if (scene.ui) { - const cancelHandler = () => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(0, 0, true); - }; - const changeLocaleHandler = (locale: string): boolean => { - try { - i18next.changeLanguage(locale); - localStorage.setItem("prLang", locale); - cancelHandler(); - // Reload the whole game to apply the new locale since also some constants are translated - window.location.reload(); - return true; - } catch (error) { - console.error("Error changing locale:", error); - return false; - } - }; - scene.ui.setOverlayMode(Mode.OPTION_SELECT, { - options: [ - { - label: "English", - handler: () => changeLocaleHandler("en") - }, - { - label: "Español", - handler: () => changeLocaleHandler("es") - }, - { - label: "Italiano", - handler: () => changeLocaleHandler("it") - }, - { - label: "Français", - handler: () => changeLocaleHandler("fr") - }, - { - label: "Deutsch", - handler: () => changeLocaleHandler("de") - }, - { - label: "Português (BR)", - handler: () => changeLocaleHandler("pt-BR") - }, - { - label: "简体中文", - handler: () => changeLocaleHandler("zh-CN") - }, - { - label: "繁體中文", - handler: () => changeLocaleHandler("zh-TW") - }, - { - label: "한국어", - handler: () => changeLocaleHandler("ko") - }, - { - label: "日本語", - handler: () => changeLocaleHandler("ja") - }, - // { - // label: "Català", - // handler: () => changeLocaleHandler("ca-ES") - // }, - { - label: i18next.t("settings:back"), - handler: () => cancelHandler() - } - ], - maxOptions: 7 - }); + case SettingKeys.Field_Volume: + scene.fieldVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); + break; + case SettingKeys.SE_Volume: + scene.seVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); + break; + case SettingKeys.UI_Volume: + scene.uiVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + break; + case SettingKeys.Music_Preference: + scene.musicPreference = value; + break; + case SettingKeys.Damage_Numbers: + scene.damageNumbersMode = value; + break; + case SettingKeys.UI_Theme: + scene.uiTheme = value; + break; + case SettingKeys.Window_Type: + updateWindowType(scene, parseInt(Setting[index].options[value].value)); + break; + case SettingKeys.Tutorials: + scene.enableTutorials = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Move_Info: + scene.enableMoveInfo = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Enable_Retries: + scene.enableRetries = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Hide_IVs: + scene.hideIvs = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Skip_Seen_Dialogues: + scene.skipSeenDialogues = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Egg_Skip: + scene.eggSkipPreference = value; + break; + case SettingKeys.Battle_Style: + scene.battleStyle = value; + break; + case SettingKeys.Show_BGM_Bar: + scene.showBgmBar = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Candy_Upgrade_Notification: + if (scene.candyUpgradeNotification === value) { + break; + } + scene.candyUpgradeNotification = value; + scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); + break; + case SettingKeys.Candy_Upgrade_Display: + scene.candyUpgradeDisplay = value; + case SettingKeys.Money_Format: + switch (Setting[index].options[value].value) { + case "Normal": + scene.moneyFormat = MoneyFormat.NORMAL; + break; + case "Abbreviated": + scene.moneyFormat = MoneyFormat.ABBREVIATED; + break; + } + scene.updateMoneyText(false); + break; + case SettingKeys.Sprite_Set: + scene.experimentalSprites = !!value; + if (value) { + scene.initExpSprites(); + } + break; + case SettingKeys.Move_Animations: + scene.moveAnimations = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Show_Moveset_Flyout: + scene.showMovesetFlyout = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Show_Arena_Flyout: + scene.showArenaFlyout = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Show_Time_Of_Day_Widget: + scene.showTimeOfDayWidget = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Time_Of_Day_Animation: + scene.timeOfDayAnimation = Setting[index].options[value].value === "Bounce" ? EaseType.BOUNCE : EaseType.BACK; + break; + case SettingKeys.Show_Stats_on_Level_Up: + scene.showLevelUpStats = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Shop_Cursor_Target: + const selectedValue = shopCursorTargetIndexMap[value]; + scene.shopCursorTarget = selectedValue; + break; + case SettingKeys.EXP_Gains_Speed: + scene.expGainsSpeed = value; + break; + case SettingKeys.EXP_Party_Display: + scene.expParty = value; + break; + case SettingKeys.HP_Bar_Speed: + scene.hpBarSpeed = value; + break; + case SettingKeys.Fusion_Palette_Swaps: + scene.fusionPaletteSwaps = !!value; + break; + case SettingKeys.Player_Gender: + if (scene.gameData) { + const female = Setting[index].options[value].value === "Girl"; + scene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE; + scene.trainer.setTexture(scene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m")); + } else { return false; } - } - break; - case SettingKeys.Shop_Overlay_Opacity: - scene.updateShopOverlayOpacity(parseInt(Setting[index].options[value].value) * .01); - break; + break; + case SettingKeys.Touch_Controls: + scene.enableTouchControls = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); + const touchControls = document.getElementById("touchControls"); + if (touchControls) { + touchControls.classList.toggle("visible", scene.enableTouchControls); + } + break; + case SettingKeys.Vibration: + scene.enableVibration = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); + break; + case SettingKeys.Type_Hints: + scene.typeHints = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Language: + if (value) { + if (scene.ui) { + const cancelHandler = () => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(0, 0, true); + }; + const changeLocaleHandler = (locale: string): boolean => { + try { + i18next.changeLanguage(locale); + localStorage.setItem("prLang", locale); + cancelHandler(); + // Reload the whole game to apply the new locale since also some constants are translated + window.location.reload(); + return true; + } catch (error) { + console.error("Error changing locale:", error); + return false; + } + }; + scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + options: [ + { + label: "English", + handler: () => changeLocaleHandler("en") + }, + { + label: "Español", + handler: () => changeLocaleHandler("es") + }, + { + label: "Italiano", + handler: () => changeLocaleHandler("it") + }, + { + label: "Français", + handler: () => changeLocaleHandler("fr") + }, + { + label: "Deutsch", + handler: () => changeLocaleHandler("de") + }, + { + label: "Português (BR)", + handler: () => changeLocaleHandler("pt-BR") + }, + { + label: "简体中文", + handler: () => changeLocaleHandler("zh-CN") + }, + { + label: "繁體中文", + handler: () => changeLocaleHandler("zh-TW") + }, + { + label: "한국어", + handler: () => changeLocaleHandler("ko") + }, + { + label: "日本語", + handler: () => changeLocaleHandler("ja") + }, + // { + // label: "Català", + // handler: () => changeLocaleHandler("ca-ES") + // }, + { + label: i18next.t("settings:back"), + handler: () => cancelHandler() + } + ], + maxOptions: 7 + }); + return false; + } + } + break; + case SettingKeys.Shop_Overlay_Opacity: + scene.updateShopOverlayOpacity(parseInt(Setting[index].options[value].value) * .01); + break; } return true; diff --git a/src/system/unlockables.ts b/src/system/unlockables.ts index 983909373fd..0a666e2c755 100644 --- a/src/system/unlockables.ts +++ b/src/system/unlockables.ts @@ -10,13 +10,13 @@ export enum Unlockables { export function getUnlockableName(unlockable: Unlockables) { switch (unlockable) { - case Unlockables.ENDLESS_MODE: - return `${GameMode.getModeName(GameModes.ENDLESS)} Mode`; - case Unlockables.MINI_BLACK_HOLE: - return i18next.t("modifierType:ModifierType.MINI_BLACK_HOLE.name"); - case Unlockables.SPLICED_ENDLESS_MODE: - return `${GameMode.getModeName(GameModes.SPLICED_ENDLESS)} Mode`; - case Unlockables.EVIOLITE: - return i18next.t("modifierType:ModifierType.EVIOLITE.name"); + case Unlockables.ENDLESS_MODE: + return `${GameMode.getModeName(GameModes.ENDLESS)} Mode`; + case Unlockables.MINI_BLACK_HOLE: + return i18next.t("modifierType:ModifierType.MINI_BLACK_HOLE.name"); + case Unlockables.SPLICED_ENDLESS_MODE: + return `${GameMode.getModeName(GameModes.SPLICED_ENDLESS)} Mode`; + case Unlockables.EVIOLITE: + return i18next.t("modifierType:ModifierType.EVIOLITE.name"); } } diff --git a/src/system/version-converter.ts b/src/system/version-converter.ts deleted file mode 100644 index 3a4416a975e..00000000000 --- a/src/system/version-converter.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { allSpecies } from "#app/data/pokemon-species"; -import { AbilityAttr, defaultStarterSpecies, DexAttr, SessionSaveData, SystemSaveData } from "./game-data"; -import { SettingKeys } from "./settings/settings"; - -const LATEST_VERSION = "1.0.5"; - -export function applySessionDataPatches(data: SessionSaveData) { - const curVersion = data.gameVersion; - - // Always sanitize money as a safeguard - data.money = Math.floor(data.money); - - if (curVersion !== LATEST_VERSION) { - switch (curVersion) { - case "1.0.0": - case "1.0.1": - case "1.0.2": - case "1.0.3": - case "1.0.4": - // --- PATCHES --- - - // Fix Battle Items, Vitamins, and Lures - data.modifiers.forEach((m) => { - if (m.className === "PokemonBaseStatModifier") { - m.className = "BaseStatModifier"; - } else if (m.className === "PokemonResetNegativeStatStageModifier") { - m.className = "ResetNegativeStatStageModifier"; - } else if (m.className === "TempBattleStatBoosterModifier") { - // Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator - if (m.typeId !== "DIRE_HIT") { - m.className = "TempStatStageBoosterModifier"; - m.typeId = "TEMP_STAT_STAGE_BOOSTER"; - - // Migration from TempBattleStat to Stat - const newStat = m.typePregenArgs[0] + 1; - m.typePregenArgs[0] = newStat; - - // From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ] - m.args = [ newStat, 5, m.args[1] ]; - } else { - m.className = "TempCritBoosterModifier"; - m.typePregenArgs = []; - - // From [ stat, battlesLeft ] to [ maxBattles, battleCount ] - m.args = [ 5, m.args[1] ]; - } - - } else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) { - let maxBattles: number; - switch (m.typeId) { - case "MAX_LURE": - maxBattles = 30; - break; - case "SUPER_LURE": - maxBattles = 15; - break; - default: - maxBattles = 10; - break; - } - - // From [ battlesLeft ] to [ maxBattles, battleCount ] - m.args = [ maxBattles, m.args[0] ]; - } - }); - - data.enemyModifiers.forEach((m) => { - if (m.className === "PokemonBaseStatModifier") { - m.className = "BaseStatModifier"; - } else if (m.className === "PokemonResetNegativeStatStageModifier") { - m.className = "ResetNegativeStatStageModifier"; - } - }); - } - - data.gameVersion = LATEST_VERSION; - } -} - -export function applySystemDataPatches(data: SystemSaveData) { - const curVersion = data.gameVersion; - if (curVersion !== LATEST_VERSION) { - switch (curVersion) { - case "1.0.0": - case "1.0.1": - case "1.0.2": - case "1.0.3": - case "1.0.4": - // --- LEGACY PATCHES --- - if (data.starterData && data.dexData) { - // Migrate ability starter data if empty for caught species - Object.keys(data.starterData).forEach(sd => { - if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) { - data.starterData[sd].abilityAttr = 1; - } - }); - } - - // Fix Legendary Stats - if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) { - data.gameStats.subLegendaryPokemonSeen = 0; - data.gameStats.subLegendaryPokemonCaught = 0; - data.gameStats.subLegendaryPokemonHatched = 0; - allSpecies.filter(s => s.subLegendary).forEach(s => { - const dexEntry = data.dexData[s.speciesId]; - data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount; - data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0); - data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount; - data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0); - data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount; - data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0); - }); - data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught); - data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught); - data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught); - } - - // --- PATCHES --- - - // Fix Starter Data - if (data.starterData && data.dexData) { - for (const starterId of defaultStarterSpecies) { - if (data.starterData[starterId]?.abilityAttr) { - data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1; - } - if (data.dexData[starterId]?.caughtAttr) { - data.dexData[starterId].caughtAttr |= DexAttr.FEMALE; - } - } - } - } - - data.gameVersion = LATEST_VERSION; - } -} - -export function applySettingsDataPatches(settings: Object) { - const curVersion = settings.hasOwnProperty("gameVersion") ? settings["gameVersion"] : "1.0.0"; - if (curVersion !== LATEST_VERSION) { - switch (curVersion) { - case "1.0.0": - case "1.0.1": - case "1.0.2": - case "1.0.3": - case "1.0.4": - // --- PATCHES --- - - // Fix Reward Cursor Target - if (settings.hasOwnProperty("REROLL_TARGET") && !settings.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) { - settings[SettingKeys.Shop_Cursor_Target] = settings["REROLL_TARGET"]; - delete settings["REROLL_TARGET"]; - localStorage.setItem("settings", JSON.stringify(settings)); - } - } - // Note that the current game version will be written at `saveSettings` - } -} diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts new file mode 100644 index 00000000000..e96afb5cbd4 --- /dev/null +++ b/src/system/version_migration/version_converter.ts @@ -0,0 +1,197 @@ +import { SessionSaveData, SystemSaveData } from "../game-data"; +import { version } from "../../../package.json"; + +// --- v1.0.4 (and below) PATCHES --- // +import * as v1_0_4 from "./versions/v1_0_4"; + +// --- v1.1.0 PATCHES --- // +import * as v1_1_0 from "./versions/v1_1_0"; + +const LATEST_VERSION = version.split(".").map(value => parseInt(value)); + +/** + * Converts incoming {@linkcode SystemSaveData} that has a version below the + * current version number listed in `package.json`. + * + * Note that no transforms act on the {@linkcode data} if its version matches + * the current version or if there are no migrations made between its version up + * to the current version. + * @param data {@linkcode SystemSaveData} + * @see {@link SystemVersionConverter} + */ +export function applySystemVersionMigration(data: SystemSaveData) { + const curVersion = data.gameVersion.split(".").map(value => parseInt(value)); + + if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { + const converter = new SystemVersionConverter(); + converter.applyStaticPreprocessors(data); + converter.applyMigration(data, curVersion); + } +} + +/** + * Converts incoming {@linkcode SessionSavaData} that has a version below the + * current version number listed in `package.json`. + * + * Note that no transforms act on the {@linkcode data} if its version matches + * the current version or if there are no migrations made between its version up + * to the current version. + * @param data {@linkcode SessionSaveData} + * @see {@link SessionVersionConverter} + */ +export function applySessionVersionMigration(data: SessionSaveData) { + const curVersion = data.gameVersion.split(".").map(value => parseInt(value)); + + if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { + const converter = new SessionVersionConverter(); + converter.applyStaticPreprocessors(data); + converter.applyMigration(data, curVersion); + } +} + +/** + * Converts incoming settings data that has a version below the + * current version number listed in `package.json`. + * + * Note that no transforms act on the {@linkcode data} if its version matches + * the current version or if there are no migrations made between its version up + * to the current version. + * @param data Settings data object + * @see {@link SettingsVersionConverter} + */ +export function applySettingsVersionMigration(data: Object) { + const gameVersion: string = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0"; + const curVersion = gameVersion.split(".").map(value => parseInt(value)); + + if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { + const converter = new SettingsVersionConverter(); + converter.applyStaticPreprocessors(data); + converter.applyMigration(data, curVersion); + } +} + +/** + * Abstract class encapsulating the logic for migrating data from a given version up to + * the current version listed in `package.json`. + * + * Note that, for any version converter, the corresponding `applyMigration` + * function would only need to be changed once when the first migration for a + * given version is introduced. Similarly, a version file (within the `versions` + * folder) would only need to be created for a version once with the appropriate + * array nomenclature. + */ +abstract class VersionConverter { + /** + * Iterates through an array of designated migration functions that are each + * called one by one to transform the data. + * @param data The data to be operated on + * @param migrationArr An array of functions that will transform the incoming data + */ + callMigrators(data: any, migrationArr: readonly any[]) { + for (const migrate of migrationArr) { + migrate(data); + } + } + + /** + * Applies any version-agnostic data sanitation as defined within the function + * body. + * @param data The data to be operated on + */ + applyStaticPreprocessors(_data: any): void { + } + + /** + * Uses the current version the incoming data to determine the starting point + * of the migration which will cascade up to the latest version, calling the + * necessary migration functions in the process. + * @param data The data to be operated on + * @param curVersion [0] Current major version + * [1] Current minor version + * [2] Current patch version + */ + abstract applyMigration(data: any, curVersion: number[]): void; +} + +/** + * Class encapsulating the logic for migrating {@linkcode SessionSaveData} from + * a given version up to the current version listed in `package.json`. + * @extends VersionConverter + */ +class SessionVersionConverter extends VersionConverter { + override applyStaticPreprocessors(data: SessionSaveData): void { + // Always sanitize money as a safeguard + data.money = Math.floor(data.money); + } + + override applyMigration(data: SessionSaveData, curVersion: number[]): void { + const [ curMajor, curMinor, curPatch ] = curVersion; + + if (curMajor === 1) { + if (curMinor === 0) { + if (curPatch <= 4) { + console.log("Applying v1.0.4 session data migration!"); + this.callMigrators(data, v1_0_4.sessionMigrators); + } + } + if (curMinor <= 1) { + console.log("Applying v1.1.0 session data migration!"); + this.callMigrators(data, v1_1_0.sessionMigrators); + } + } + + console.log(`Session data successfully migrated to v${version}!`); + } +} + +/** + * Class encapsulating the logic for migrating {@linkcode SystemSaveData} from + * a given version up to the current version listed in `package.json`. + * @extends VersionConverter + */ +class SystemVersionConverter extends VersionConverter { + override applyMigration(data: SystemSaveData, curVersion: number[]): void { + const [ curMajor, curMinor, curPatch ] = curVersion; + + if (curMajor === 1) { + if (curMinor === 0) { + if (curPatch <= 4) { + console.log("Applying v1.0.4 system data migraton!"); + this.callMigrators(data, v1_0_4.systemMigrators); + } + } + if (curMinor <= 1) { + console.log("Applying v1.1.0 system data migraton!"); + this.callMigrators(data, v1_1_0.systemMigrators); + } + } + + console.log(`System data successfully migrated to v${version}!`); + } +} + +/** + * Class encapsulating the logic for migrating settings data from + * a given version up to the current version listed in `package.json`. + * @extends VersionConverter + */ +class SettingsVersionConverter extends VersionConverter { + override applyMigration(data: Object, curVersion: number[]): void { + const [ curMajor, curMinor, curPatch ] = curVersion; + + if (curMajor === 1) { + if (curMinor === 0) { + if (curPatch <= 4) { + console.log("Applying v1.0.4 settings data migraton!"); + this.callMigrators(data, v1_0_4.settingsMigrators); + } + } + if (curMinor <= 1) { + console.log("Applying v1.1.0 settings data migraton!"); + this.callMigrators(data, v1_1_0.settingsMigrators); + } + } + + console.log(`System data successfully migrated to v${version}!`); + } +} diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts new file mode 100644 index 00000000000..f16b6bcb6bb --- /dev/null +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -0,0 +1,135 @@ +import { SettingKeys } from "../../settings/settings"; +import { AbilityAttr, defaultStarterSpecies, DexAttr, SystemSaveData, SessionSaveData } from "../../game-data"; +import { allSpecies } from "../../../data/pokemon-species"; + +export const systemMigrators = [ + /** + * Migrate ability starter data if empty for caught species. + * @param data {@linkcode SystemSaveData} + */ + function migrateAbilityData(data: SystemSaveData) { + if (data.starterData && data.dexData) { + Object.keys(data.starterData).forEach(sd => { + if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) { + data.starterData[sd].abilityAttr = 1; + } + }); + } + }, + + /** + * Populate legendary Pokémon statistics if they are missing. + * @param data {@linkcode SystemSaveData} + */ + function fixLegendaryStats(data: SystemSaveData) { + if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) { + data.gameStats.subLegendaryPokemonSeen = 0; + data.gameStats.subLegendaryPokemonCaught = 0; + data.gameStats.subLegendaryPokemonHatched = 0; + allSpecies.filter(s => s.subLegendary).forEach(s => { + const dexEntry = data.dexData[s.speciesId]; + data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount; + data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0); + data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount; + data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0); + data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount; + data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0); + }); + data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught); + data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught); + data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught); + } + }, + + /** + * Unlock all starters' first ability and female gender option. + * @param data {@linkcode SystemSaveData} + */ + function fixStarterData(data: SystemSaveData) { + for (const starterId of defaultStarterSpecies) { + if (data.starterData[starterId]?.abilityAttr) { + data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1; + } + if (data.dexData[starterId]?.caughtAttr) { + data.dexData[starterId].caughtAttr |= DexAttr.FEMALE; + } + } + } +] as const; + +export const settingsMigrators = [ + /** + * Migrate from "REROLL_TARGET" property to {@linkcode + * SettingKeys.Shop_Cursor_Target}. + * @param data the `settings` object + */ + function fixRerollTarget(data: Object) { + if (data.hasOwnProperty("REROLL_TARGET") && !data.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) { + data[SettingKeys.Shop_Cursor_Target] = data["REROLL_TARGET"]; + delete data["REROLL_TARGET"]; + localStorage.setItem("settings", JSON.stringify(data)); + } + } +] as const; + +export const sessionMigrators = [ + /** + * Converts old lapsing modifiers (battle items, lures, and Dire Hit) and + * other miscellaneous modifiers (vitamins, White Herb) to any new class + * names and/or change in reload arguments. + * @param data {@linkcode SessionSaveData} + */ + function migrateModifiers(data: SessionSaveData) { + data.modifiers.forEach((m) => { + if (m.className === "PokemonBaseStatModifier") { + m.className = "BaseStatModifier"; + } else if (m.className === "PokemonResetNegativeStatStageModifier") { + m.className = "ResetNegativeStatStageModifier"; + } else if (m.className === "TempBattleStatBoosterModifier") { + const maxBattles = 5; + // Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator + if (m.typeId !== "DIRE_HIT") { + m.className = "TempStatStageBoosterModifier"; + m.typeId = "TEMP_STAT_STAGE_BOOSTER"; + + // Migration from TempBattleStat to Stat + const newStat = m.typePregenArgs[0] + 1; + m.typePregenArgs[0] = newStat; + + // From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ] + m.args = [ newStat, maxBattles, Math.min(m.args[1], maxBattles) ]; + } else { + m.className = "TempCritBoosterModifier"; + m.typePregenArgs = []; + + // From [ stat, battlesLeft ] to [ maxBattles, battleCount ] + m.args = [ maxBattles, Math.min(m.args[1], maxBattles) ]; + } + } else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) { + let maxBattles: number; + switch (m.typeId) { + case "MAX_LURE": + maxBattles = 30; + break; + case "SUPER_LURE": + maxBattles = 15; + break; + default: + maxBattles = 10; + break; + } + + // From [ battlesLeft ] to [ maxBattles, battleCount ] + m.args = [ maxBattles, Math.min(m.args[0], maxBattles) ]; + } + }); + + data.enemyModifiers.forEach((m) => { + if (m.className === "PokemonBaseStatModifier") { + m.className = "BaseStatModifier"; + } else if (m.className === "PokemonResetNegativeStatStageModifier") { + m.className = "ResetNegativeStatStageModifier"; + } + }); + } +] as const; diff --git a/src/system/version_migration/versions/v1_1_0.ts b/src/system/version_migration/versions/v1_1_0.ts new file mode 100644 index 00000000000..aac554c4531 --- /dev/null +++ b/src/system/version_migration/versions/v1_1_0.ts @@ -0,0 +1,32 @@ +import { SessionSaveData } from "../../game-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; + +export const systemMigrators = [] as const; + +export const settingsMigrators = [] as const; + +export const sessionMigrators = [ + /** + * Converts old Pokemon natureOverride and mysteryEncounterData + * to use the new conjoined {@linkcode Pokemon.customPokemonData} structure instead. + * @param data {@linkcode SessionSaveData} + */ + function migrateCustomPokemonDataAndNatureOverrides(data: SessionSaveData) { + // Fix Pokemon nature overrides and custom data migration + data.party.forEach(pokemon => { + if (pokemon["mysteryEncounterPokemonData"]) { + pokemon.customPokemonData = new CustomPokemonData(pokemon["mysteryEncounterPokemonData"]); + pokemon["mysteryEncounterPokemonData"] = null; + } + if (pokemon["fusionMysteryEncounterPokemonData"]) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(pokemon["fusionMysteryEncounterPokemonData"]); + pokemon["fusionMysteryEncounterPokemonData"] = null; + } + pokemon.customPokemonData = pokemon.customPokemonData ?? new CustomPokemonData(); + if (pokemon["natureOverride"] && pokemon["natureOverride"] >= 0) { + pokemon.customPokemonData.nature = pokemon["natureOverride"]; + pokemon["natureOverride"] = -1; + } + }); + } +] as const; diff --git a/src/system/voucher.ts b/src/system/voucher.ts index aca7b9fc2f2..b38fd528c9f 100644 --- a/src/system/voucher.ts +++ b/src/system/voucher.ts @@ -45,41 +45,41 @@ export class Voucher { getTier(): AchvTier { switch (this.voucherType) { - case VoucherType.REGULAR: - return AchvTier.COMMON; - case VoucherType.PLUS: - return AchvTier.GREAT; - case VoucherType.PREMIUM: - return AchvTier.ULTRA; - case VoucherType.GOLDEN: - return AchvTier.ROGUE; + case VoucherType.REGULAR: + return AchvTier.COMMON; + case VoucherType.PLUS: + return AchvTier.GREAT; + case VoucherType.PREMIUM: + return AchvTier.ULTRA; + case VoucherType.GOLDEN: + return AchvTier.ROGUE; } } } export function getVoucherTypeName(voucherType: VoucherType): string { switch (voucherType) { - case VoucherType.REGULAR: - return i18next.t("voucher:eggVoucher"); - case VoucherType.PLUS: - return i18next.t("voucher:eggVoucherPlus"); - case VoucherType.PREMIUM: - return i18next.t("voucher:eggVoucherPremium"); - case VoucherType.GOLDEN: - return i18next.t("voucher:eggVoucherGold"); + case VoucherType.REGULAR: + return i18next.t("voucher:eggVoucher"); + case VoucherType.PLUS: + return i18next.t("voucher:eggVoucherPlus"); + case VoucherType.PREMIUM: + return i18next.t("voucher:eggVoucherPremium"); + case VoucherType.GOLDEN: + return i18next.t("voucher:eggVoucherGold"); } } export function getVoucherTypeIcon(voucherType: VoucherType): string { switch (voucherType) { - case VoucherType.REGULAR: - return "coupon"; - case VoucherType.PLUS: - return "pair_of_tickets"; - case VoucherType.PREMIUM: - return "mystic_ticket"; - case VoucherType.GOLDEN: - return "golden_mystic_ticket"; + case VoucherType.REGULAR: + return "coupon"; + case VoucherType.PLUS: + return "pair_of_tickets"; + case VoucherType.PREMIUM: + return "mystic_ticket"; + case VoucherType.GOLDEN: + return "golden_mystic_ticket"; } } diff --git a/src/test/abilities/ability_timing.test.ts b/src/test/abilities/ability_timing.test.ts index 1472f9eb429..e3264c2c1a8 100644 --- a/src/test/abilities/ability_timing.test.ts +++ b/src/test/abilities/ability_timing.test.ts @@ -1,13 +1,13 @@ import { BattleStyle } from "#app/enums/battle-style"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; -import i18next, { initI18n } from "#app/plugins/i18n"; +import i18next from "#app/plugins/i18n"; import { Mode } from "#app/ui/ui"; import { Abilities } from "#enums/abilities"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Ability Timing", () => { @@ -32,11 +32,10 @@ describe("Ability Timing", () => { .enemySpecies(Species.MAGIKARP) .enemyAbility(Abilities.INTIMIDATE) .ability(Abilities.BALL_FETCH); + vi.spyOn(i18next, "t"); }); it("should trigger after switch check", async () => { - initI18n(); - i18next.changeLanguage("en"); game.settings.battleStyle = BattleStyle.SWITCH; await game.classicMode.runToSummon([ Species.EEVEE, Species.FEEBAS ]); @@ -46,7 +45,6 @@ describe("Ability Timing", () => { }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)); await game.phaseInterceptor.to("MessagePhase"); - const message = game.textInterceptor.getLatestMessage(); - expect(message).toContain("battle:statFell"); + expect(i18next.t).toHaveBeenCalledWith("battle:statFell", expect.objectContaining({ count: 1 })); }, 5000); }); diff --git a/src/test/abilities/battle_bond.test.ts b/src/test/abilities/battle_bond.test.ts index c7dffeb150a..283fb0d0f14 100644 --- a/src/test/abilities/battle_bond.test.ts +++ b/src/test/abilities/battle_bond.test.ts @@ -1,17 +1,19 @@ +import { allMoves, MultiHitAttr, MultiHitType } from "#app/data/move"; import { Status, StatusEffect } from "#app/data/status-effect"; -import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; -import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Abilities - BATTLE BOND", () => { let phaserGame: Phaser.Game; let game: GameManager; + const baseForm = 1; + const ashForm = 2; + beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -24,40 +26,68 @@ describe("Abilities - BATTLE BOND", () => { beforeEach(() => { game = new GameManager(phaserGame); - const moveToUse = Moves.SPLASH; - game.override.battleType("single"); - game.override.ability(Abilities.BATTLE_BOND); - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); + game.override.battleType("single") + .startingWave(4) // Leads to arena reset on Wave 5 trainer battle + .ability(Abilities.BATTLE_BOND) + .starterForms({ [Species.GRENINJA]: ashForm, }) + .moveset([ Moves.SPLASH, Moves.WATER_SHURIKEN ]) + .enemySpecies(Species.BULBASAUR) + .enemyMoveset(Moves.SPLASH) + .startingLevel(100) // Avoid levelling up + .enemyLevel(1000); // Avoid opponent dying before `doKillOpponents()` }); - test( - "check if fainted pokemon switches to base form on arena reset", - async () => { - const baseForm = 1; - const ashForm = 2; - game.override.startingWave(4); - game.override.starterForms({ - [Species.GRENINJA]: ashForm, - }); + it("check if fainted pokemon switches to base form on arena reset", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP, Species.GRENINJA ]); - await game.startBattle([ Species.MAGIKARP, Species.GRENINJA ]); + const greninja = game.scene.getParty()[1]; + expect(greninja.formIndex).toBe(ashForm); - const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA); - expect(greninja).toBeDefined(); - expect(greninja!.formIndex).toBe(ashForm); + greninja.hp = 0; + greninja.status = new Status(StatusEffect.FAINT); + expect(greninja.isFainted()).toBe(true); - greninja!.hp = 0; - greninja!.status = new Status(StatusEffect.FAINT); - expect(greninja!.isFainted()).toBe(true); + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to("TurnEndPhase"); + game.doSelectModifier(); + await game.phaseInterceptor.to("QuietFormChangePhase"); - game.move.select(Moves.SPLASH); - await game.doKillOpponents(); - await game.phaseInterceptor.to(TurnEndPhase); - game.doSelectModifier(); - await game.phaseInterceptor.to(QuietFormChangePhase); + expect(greninja.formIndex).toBe(baseForm); + }); - expect(greninja!.formIndex).toBe(baseForm); - }, - ); + it("should not keep buffing Water Shuriken after Greninja switches to base form", async () => { + await game.classicMode.startBattle([ Species.GRENINJA ]); + + const waterShuriken = allMoves[Moves.WATER_SHURIKEN]; + vi.spyOn(waterShuriken, "calculateBattlePower"); + + let actualMultiHitType: MultiHitType | null = null; + const multiHitAttr = waterShuriken.getAttrs(MultiHitAttr)[0]; + vi.spyOn(multiHitAttr, "getHitCount").mockImplementation(() => { + actualMultiHitType = multiHitAttr.getMultiHitType(); + return 3; + }); + + // Wave 4: Use Water Shuriken in Ash form + let expectedBattlePower = 20; + let expectedMultiHitType = MultiHitType._3; + + game.move.select(Moves.WATER_SHURIKEN); + await game.phaseInterceptor.to("BerryPhase", false); + expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower); + expect(actualMultiHitType).toBe(expectedMultiHitType); + + await game.doKillOpponents(); + await game.toNextWave(); + + // Wave 5: Use Water Shuriken in base form + expectedBattlePower = 15; + expectedMultiHitType = MultiHitType._2_TO_5; + + game.move.select(Moves.WATER_SHURIKEN); + await game.phaseInterceptor.to("BerryPhase", false); + expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower); + expect(actualMultiHitType).toBe(expectedMultiHitType); + }); }); diff --git a/src/test/abilities/competitive.test.ts b/src/test/abilities/competitive.test.ts new file mode 100644 index 00000000000..ecb276a1b8d --- /dev/null +++ b/src/test/abilities/competitive.test.ts @@ -0,0 +1,72 @@ +import { Stat } from "#enums/stat"; +import { TurnInitPhase } from "#app/phases/turn-init-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Competitive", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override.battleType("single") + .enemySpecies(Species.BEEDRILL) + .enemyMoveset(Moves.TICKLE) + .startingLevel(1) + .moveset([ Moves.SPLASH, Moves.CLOSE_COMBAT ]) + .ability(Abilities.COMPETITIVE); + }); + + it("lower atk and def by 1 via tickle, then increase spatk by 4 via competitive", async () => { + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(4); + }); + + it("lowering your own stats should not trigger competitive", async () => { + game.override.enemyMoveset(Moves.SPLASH); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.CLOSE_COMBAT); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(0); + }); + + it("white herb should remove only the negative effects", async () => { + game.override.startingHeldItems([{ name: "WHITE_HERB" }]); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(4); + }); +}); diff --git a/src/test/abilities/defiant.test.ts b/src/test/abilities/defiant.test.ts new file mode 100644 index 00000000000..aa8d250dad7 --- /dev/null +++ b/src/test/abilities/defiant.test.ts @@ -0,0 +1,70 @@ +import { Stat } from "#enums/stat"; +import { TurnInitPhase } from "#app/phases/turn-init-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Defiant", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override.battleType("single") + .enemySpecies(Species.BEEDRILL) + .enemyMoveset(Moves.TICKLE) + .startingLevel(1) + .moveset([ Moves.SPLASH, Moves.CLOSE_COMBAT ]) + .ability(Abilities.DEFIANT); + }); + + it("lower atk and def by 1 via tickle, then increase atk by 4 via defiant", async () => { + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(3); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); + }); + + it("lowering your own stats should not trigger defiant", async () => { + game.override.enemyMoveset(Moves.SPLASH); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.CLOSE_COMBAT); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("white herb should remove only the negative effects", async () => { + game.override.startingHeldItems([{ name: "WHITE_HERB" }]); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(3); + }); +}); diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts index a295dd61443..0241aa4b9ea 100644 --- a/src/test/abilities/disguise.test.ts +++ b/src/test/abilities/disguise.test.ts @@ -1,8 +1,9 @@ +import { BattlerIndex } from "#app/battle"; +import { StatusEffect } from "#app/data/status-effect"; import { toDmgValue } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { StatusEffect } from "#app/data/status-effect"; import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -222,4 +223,17 @@ describe("Abilities - Disguise", () => { expect(mimikyu.formIndex).toBe(bustedForm); expect(mimikyu.hp).toBe(maxHp - disguiseDamage); }); + + it("doesn't trigger if user is behind a substitute", async () => { + game.override + .enemyMoveset(Moves.SUBSTITUTE) + .moveset(Moves.POWER_TRIP); + await game.classicMode.startBattle(); + + game.move.select(Moves.POWER_TRIP); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.formIndex).toBe(disguisedForm); + }); }); diff --git a/src/test/abilities/gulp_missile.test.ts b/src/test/abilities/gulp_missile.test.ts index 1ca208996b5..01b68d0c89d 100644 --- a/src/test/abilities/gulp_missile.test.ts +++ b/src/test/abilities/gulp_missile.test.ts @@ -1,13 +1,14 @@ -import { BattlerTagType } from "#enums/battler-tag-type"; -import { StatusEffect } from "#enums/status-effect"; +import { BattlerIndex } from "#app/battle"; import Pokemon from "#app/field/pokemon"; -import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { Stat } from "#enums/stat"; describe("Abilities - Gulp Missile", () => { let phaserGame: Phaser.Game; @@ -40,8 +41,9 @@ describe("Abilities - Gulp Missile", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override + .disableCrits() .battleType("single") - .moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH ]) + .moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH, Moves.SUBSTITUTE ]) .enemySpecies(Species.SNORLAX) .enemyAbility(Abilities.BALL_FETCH) .enemyMoveset(Moves.SPLASH) @@ -234,6 +236,25 @@ describe("Abilities - Gulp Missile", () => { expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(-1); }); + it("doesn't trigger if user is behind a substitute", async () => { + game.override + .enemyAbility(Abilities.STURDY) + .enemyMoveset([ Moves.SPLASH, Moves.POWER_TRIP ]); + await game.classicMode.startBattle([ Species.CRAMORANT ]); + + game.move.select(Moves.SURF); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM); + + game.move.select(Moves.SUBSTITUTE); + await game.forceEnemyMove(Moves.POWER_TRIP); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM); + }); it("cannot be suppressed", async () => { game.override.enemyMoveset(Moves.GASTRO_ACID); diff --git a/src/test/abilities/ice_face.test.ts b/src/test/abilities/ice_face.test.ts index 723d5e8d855..1c7f7bd6093 100644 --- a/src/test/abilities/ice_face.test.ts +++ b/src/test/abilities/ice_face.test.ts @@ -1,3 +1,4 @@ +import { BattlerIndex } from "#app/battle"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; @@ -36,7 +37,7 @@ describe("Abilities - Ice Face", () => { }); it("takes no damage from physical move and transforms to Noice", async () => { - await game.startBattle([ Species.HITMONLEE ]); + await game.classicMode.startBattle([ Species.HITMONLEE ]); game.move.select(Moves.TACKLE); @@ -52,7 +53,7 @@ describe("Abilities - Ice Face", () => { it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => { game.override.moveset([ Moves.SURGING_STRIKES ]); game.override.enemyLevel(1); - await game.startBattle([ Species.HITMONLEE ]); + await game.classicMode.startBattle([ Species.HITMONLEE ]); game.move.select(Moves.SURGING_STRIKES); @@ -78,7 +79,7 @@ describe("Abilities - Ice Face", () => { }); it("takes damage from special moves", async () => { - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.ICE_BEAM); @@ -92,7 +93,7 @@ describe("Abilities - Ice Face", () => { }); it("takes effects from status moves", async () => { - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.TOXIC_THREAD); @@ -108,7 +109,7 @@ describe("Abilities - Ice Face", () => { game.override.moveset([ Moves.QUICK_ATTACK ]); game.override.enemyMoveset([ Moves.HAIL, Moves.HAIL, Moves.HAIL, Moves.HAIL ]); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.QUICK_ATTACK); @@ -130,7 +131,7 @@ describe("Abilities - Ice Face", () => { game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); game.override.moveset([ Moves.SNOWSCAPE ]); - await game.startBattle([ Species.EISCUE, Species.NINJASK ]); + await game.classicMode.startBattle([ Species.EISCUE, Species.NINJASK ]); game.move.select(Moves.SNOWSCAPE); @@ -157,7 +158,7 @@ describe("Abilities - Ice Face", () => { game.override.enemySpecies(Species.SHUCKLE); game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); - await game.startBattle([ Species.EISCUE ]); + await game.classicMode.startBattle([ Species.EISCUE ]); game.move.select(Moves.HAIL); const eiscue = game.scene.getPlayerPokemon()!; @@ -176,7 +177,7 @@ describe("Abilities - Ice Face", () => { it("persists form change when switched out", async () => { game.override.enemyMoveset([ Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK ]); - await game.startBattle([ Species.EISCUE, Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.EISCUE, Species.MAGIKARP ]); game.move.select(Moves.ICE_BEAM); @@ -205,7 +206,7 @@ describe("Abilities - Ice Face", () => { [Species.EISCUE]: noiceForm, }); - await game.startBattle([ Species.EISCUE ]); + await game.classicMode.startBattle([ Species.EISCUE ]); const eiscue = game.scene.getPlayerPokemon()!; @@ -222,10 +223,23 @@ describe("Abilities - Ice Face", () => { expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); }); + it("doesn't trigger if user is behind a substitute", async () => { + game.override + .enemyMoveset(Moves.SUBSTITUTE) + .moveset(Moves.POWER_TRIP); + await game.classicMode.startBattle(); + + game.move.select(Moves.POWER_TRIP); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.formIndex).toBe(icefaceForm); + }); + it("cannot be suppressed", async () => { game.override.moveset([ Moves.GASTRO_ACID ]); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.GASTRO_ACID); @@ -241,7 +255,7 @@ describe("Abilities - Ice Face", () => { it("cannot be swapped with another ability", async () => { game.override.moveset([ Moves.SKILL_SWAP ]); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.SKILL_SWAP); @@ -257,7 +271,7 @@ describe("Abilities - Ice Face", () => { it("cannot be copied", async () => { game.override.ability(Abilities.TRACE); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.SIMPLE_BEAM); diff --git a/src/test/abilities/power_construct.test.ts b/src/test/abilities/power_construct.test.ts index 662f5d06258..1a9e7d4818a 100644 --- a/src/test/abilities/power_construct.test.ts +++ b/src/test/abilities/power_construct.test.ts @@ -32,7 +32,7 @@ describe("Abilities - POWER CONSTRUCT", () => { }); test( - "check if fainted pokemon switches to base form on arena reset", + "check if fainted 50% Power Construct Pokemon switches to base form on arena reset", async () => { const baseForm = 2, completeForm = 4; @@ -41,7 +41,37 @@ describe("Abilities - POWER CONSTRUCT", () => { [Species.ZYGARDE]: completeForm, }); - await game.startBattle([ Species.MAGIKARP, Species.ZYGARDE ]); + await game.classicMode.startBattle([ Species.MAGIKARP, Species.ZYGARDE ]); + + const zygarde = game.scene.getParty().find((p) => p.species.speciesId === Species.ZYGARDE); + expect(zygarde).not.toBe(undefined); + expect(zygarde!.formIndex).toBe(completeForm); + + zygarde!.hp = 0; + zygarde!.status = new Status(StatusEffect.FAINT); + expect(zygarde!.isFainted()).toBe(true); + + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to(TurnEndPhase); + game.doSelectModifier(); + await game.phaseInterceptor.to(QuietFormChangePhase); + + expect(zygarde!.formIndex).toBe(baseForm); + }, + ); + + test( + "check if fainted 10% Power Construct Pokemon switches to base form on arena reset", + async () => { + const baseForm = 3, + completeForm = 5; + game.override.startingWave(4); + game.override.starterForms({ + [Species.ZYGARDE]: completeForm, + }); + + await game.classicMode.startBattle([ Species.MAGIKARP, Species.ZYGARDE ]); const zygarde = game.scene.getParty().find((p) => p.species.speciesId === Species.ZYGARDE); expect(zygarde).not.toBe(undefined); diff --git a/src/test/abilities/sheer_force.test.ts b/src/test/abilities/sheer_force.test.ts index a3add0a9964..a2600476d6d 100644 --- a/src/test/abilities/sheer_force.test.ts +++ b/src/test/abilities/sheer_force.test.ts @@ -9,6 +9,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { allMoves } from "#app/data/move"; describe("Abilities - Sheer Force", () => { @@ -174,5 +175,31 @@ describe("Abilities - Sheer Force", () => { }, 20000); + it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => { + const moveToUse = Moves.CRUNCH; + game.override.enemyAbility(Abilities.COLOR_CHANGE) + .ability(Abilities.COLOR_CHANGE) + .moveset(moveToUse) + .enemyMoveset(moveToUse); + + await game.classicMode.startBattle([ + Species.PIDGEOT + ]); + + const pidgeot = game.scene.getParty()[0]; + const onix = game.scene.getEnemyParty()[0]; + + pidgeot.stats[Stat.DEF] = 10000; + onix.stats[Stat.DEF] = 10000; + + game.move.select(moveToUse); + await game.toNextTurn(); + + // Check that both Pokemon's Color Change activated + const expectedTypes = [ allMoves[moveToUse].type ]; + expect(pidgeot.getTypes()).toStrictEqual(expectedTypes); + expect(onix.getTypes()).toStrictEqual(expectedTypes); + }); + //TODO King's Rock Interaction Unit Test }); diff --git a/src/test/abilities/volt_absorb.test.ts b/src/test/abilities/volt_absorb.test.ts index 07907a34566..ec82b00ec5a 100644 --- a/src/test/abilities/volt_absorb.test.ts +++ b/src/test/abilities/volt_absorb.test.ts @@ -71,4 +71,23 @@ describe("Abilities - Volt Absorb", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); }); + it("regardless of accuracy should not trigger on pokemon in semi invulnerable state", async () => { + game.override.moveset(Moves.THUNDERBOLT); + game.override.enemyMoveset(Moves.DIVE); + game.override.enemySpecies(Species.MAGIKARP); + game.override.enemyAbility(Abilities.VOLT_ABSORB); + + await game.classicMode.startBattle(); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.THUNDERBOLT); + enemyPokemon.hp = enemyPokemon.hp - 1; + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + await game.move.forceMiss(); + await game.phaseInterceptor.to("BerryPhase", false); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + }); }); diff --git a/src/test/eggs/egg.test.ts b/src/test/eggs/egg.test.ts index cf53cca5af8..6f57af63e6b 100644 --- a/src/test/eggs/egg.test.ts +++ b/src/test/eggs/egg.test.ts @@ -55,7 +55,7 @@ describe("Egg Generation Tests", () => { let gachaSpeciesCount = 0; for (let i = 0; i < EGG_HATCH_COUNT; i++) { - const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.MASTER }).generatePlayerPokemon(scene).species.speciesId; + const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.LEGENDARY }).generatePlayerPokemon(scene).species.speciesId; if (result === expectedSpecies) { gachaSpeciesCount++; } @@ -82,7 +82,7 @@ describe("Egg Generation Tests", () => { }); it("should return an rare tier egg", () => { const scene = game.scene; - const expectedTier = EggTier.GREAT; + const expectedTier = EggTier.RARE; const result = new Egg({ scene, tier: expectedTier }).tier; @@ -90,7 +90,7 @@ describe("Egg Generation Tests", () => { }); it("should return an epic tier egg", () => { const scene = game.scene; - const expectedTier = EggTier.ULTRA; + const expectedTier = EggTier.EPIC; const result = new Egg({ scene, tier: expectedTier }).tier; @@ -98,7 +98,7 @@ describe("Egg Generation Tests", () => { }); it("should return an legendary tier egg", () => { const scene = game.scene; - const expectedTier = EggTier.MASTER; + const expectedTier = EggTier.LEGENDARY; const result = new Egg({ scene, tier: expectedTier }).tier; @@ -200,7 +200,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const expectedEggTier = EggTier.COMMON; - const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).tier; + const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).tier; expect(result).toBe(expectedEggTier); }); @@ -208,7 +208,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const expectedHatchWaves = 10; - const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).hatchWaves; + const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).hatchWaves; expect(result).toBe(expectedHatchWaves); }); @@ -229,7 +229,7 @@ describe("Egg Generation Tests", () => { const result = new EggData(legacyEgg).toEgg(); - expect(result.tier).toBe(EggTier.GREAT); + expect(result.tier).toBe(EggTier.RARE); expect(result.id).toBe(legacyEgg.id); expect(result.timestamp).toBe(legacyEgg.timestamp); expect(result.hatchWaves).toBe(legacyEgg.hatchWaves); @@ -241,9 +241,9 @@ describe("Egg Generation Tests", () => { new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.COMMON }); - expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1); - expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1); - expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 1); + expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1); + expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1); + expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 1); }); it("should increase legendary egg pity by two", () => { const scene = game.scene; @@ -251,9 +251,9 @@ describe("Egg Generation Tests", () => { new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true, tier: EggTier.COMMON }); - expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1); - expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1); - expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 2); + expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1); + expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1); + expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 2); }); it("should not increase manaphy egg count if bulbasaurs are pulled", () => { const scene = game.scene; @@ -277,7 +277,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const startingRareEggsPulled = scene.gameData.gameStats.rareEggsPulled; - new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.GREAT }); + new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.RARE }); expect(scene.gameData.gameStats.rareEggsPulled).toBe(startingRareEggsPulled + 1); }); @@ -285,7 +285,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const startingEpicEggsPulled = scene.gameData.gameStats.epicEggsPulled; - new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.ULTRA }); + new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.EPIC }); expect(scene.gameData.gameStats.epicEggsPulled).toBe(startingEpicEggsPulled + 1); }); @@ -293,7 +293,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const startingLegendaryEggsPulled = scene.gameData.gameStats.legendaryEggsPulled; - new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.MASTER }); + new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.LEGENDARY }); expect(scene.gameData.gameStats.legendaryEggsPulled).toBe(startingLegendaryEggsPulled + 1); }); @@ -301,8 +301,8 @@ describe("Egg Generation Tests", () => { vi.spyOn(Utils, "randInt").mockReturnValue(1); const scene = game.scene; - const expectedTier1 = EggTier.MASTER; - const expectedTier2 = EggTier.ULTRA; + const expectedTier1 = EggTier.LEGENDARY; + const expectedTier2 = EggTier.EPIC; const result1 = new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true }).tier; const result2 = new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true }).tier; diff --git a/src/test/items/grip_claw.test.ts b/src/test/items/grip_claw.test.ts index 9d44a9e4672..2909549af87 100644 --- a/src/test/items/grip_claw.test.ts +++ b/src/test/items/grip_claw.test.ts @@ -1,16 +1,14 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/move"; -import { Abilities } from "#app/enums/abilities"; -import { BerryType } from "#app/enums/berry-type"; -import { Moves } from "#app/enums/moves"; -import { Species } from "#app/enums/species"; -import { MoveEndPhase } from "#app/phases/move-end-phase"; +import Pokemon from "#app/field/pokemon"; +import { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; +import { Abilities } from "#enums/abilities"; +import { BerryType } from "#enums/berry-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -// 20 seconds - describe("Items - Grip Claw", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -30,39 +28,85 @@ describe("Items - Grip Claw", () => { game.override .battleType("double") - .moveset([ Moves.POPULATION_BOMB, Moves.SPLASH ]) + .moveset([ Moves.TACKLE, Moves.SPLASH, Moves.ATTRACT ]) .startingHeldItems([ - { name: "GRIP_CLAW", count: 5 }, // TODO: Find a way to mock the steal chance of grip claw - { name: "MULTI_LENS", count: 3 }, + { name: "GRIP_CLAW", count: 1 }, ]) .enemySpecies(Species.SNORLAX) - .ability(Abilities.KLUTZ) + .enemyAbility(Abilities.UNNERVE) + .ability(Abilities.UNNERVE) .enemyMoveset(Moves.SPLASH) .enemyHeldItems([ { name: "BERRY", type: BerryType.SITRUS, count: 2 }, { name: "BERRY", type: BerryType.LUM, count: 2 }, ]) - .startingLevel(100) .enemyLevel(100); - vi.spyOn(allMoves[Moves.POPULATION_BOMB], "accuracy", "get").mockReturnValue(100); }); - it( - "should only steal items from the attack target", - async () => { - await game.startBattle([ Species.PANSEAR, Species.ROWLET ]); + it("should steal items on contact and only from the attack target", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]); - const enemyPokemon = game.scene.getEnemyField(); + const [ playerPokemon, ] = game.scene.getPlayerField(); - const enemyHeldItemCt = enemyPokemon.map(p => p.getHeldItems.length); + const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier; + vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100); - game.move.select(Moves.POPULATION_BOMB, 0, BattlerIndex.ENEMY); - game.move.select(Moves.SPLASH, 1); + const enemyPokemon = game.scene.getEnemyField(); - await game.phaseInterceptor.to(MoveEndPhase, false); + const playerHeldItemCount = getHeldItemCount(playerPokemon); + const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]); + expect(enemy2HeldItemCount).toBeGreaterThan(0); - expect(enemyPokemon[1].getHeldItems.length).toBe(enemyHeldItemCt[1]); - } - ); + game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY_2); + game.move.select(Moves.SPLASH, 1); + + await game.phaseInterceptor.to("BerryPhase", false); + + const playerHeldItemCountAfter = getHeldItemCount(playerPokemon); + const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]); + + expect(playerHeldItemCountAfter).toBe(playerHeldItemCount + 1); + expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount); + expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount - 1); + }); + + it("should not steal items when using a targetted, non attack move", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]); + + const [ playerPokemon, ] = game.scene.getPlayerField(); + + const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier; + vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100); + + const enemyPokemon = game.scene.getEnemyField(); + + const playerHeldItemCount = getHeldItemCount(playerPokemon); + const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]); + expect(enemy2HeldItemCount).toBeGreaterThan(0); + + game.move.select(Moves.ATTRACT, 0, BattlerIndex.ENEMY_2); + game.move.select(Moves.SPLASH, 1); + + await game.phaseInterceptor.to("BerryPhase", false); + + const playerHeldItemCountAfter = getHeldItemCount(playerPokemon); + const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]); + + expect(playerHeldItemCountAfter).toBe(playerHeldItemCount); + expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount); + expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount); + }); }); + +/* + * Gets the total number of items a Pokemon holds + */ +function getHeldItemCount(pokemon: Pokemon) { + return pokemon.getHeldItems().reduce((currentTotal, item) => currentTotal + item.getStackCount(), 0); +} + diff --git a/src/test/items/lock_capsule.test.ts b/src/test/items/lock_capsule.test.ts index 2667ecea2dc..0b6534b5eaf 100644 --- a/src/test/items/lock_capsule.test.ts +++ b/src/test/items/lock_capsule.test.ts @@ -1,7 +1,8 @@ import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; -import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; +import { ModifierTier } from "#app/modifier/modifier-tier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { Mode } from "#app/ui/ui"; import GameManager from "#test/utils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -32,15 +33,16 @@ describe("Items - Lock Capsule", () => { }); it("doesn't set the cost of common tier items to 0", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); + game.scene.overridePhase(new SelectModifierPhase(game.scene, 0, undefined, { guaranteedModifierTiers: [ ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.COMMON ], fillRemaining: false })); - game.move.select(Moves.SURF); - await game.phaseInterceptor.to(SelectModifierPhase, false); + game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => { + const selectModifierPhase = game.scene.getCurrentPhase() as SelectModifierPhase; + const rerollCost = selectModifierPhase.getRerollCost(true); + expect(rerollCost).toBe(150); + }); - const rewards = game.scene.getCurrentPhase() as SelectModifierPhase; - const potion = new ModifierTypeOption(modifierTypes.POTION(), 0, 40); // Common tier item - const rerollCost = rewards.getRerollCost([ potion, potion, potion ], true); - - expect(rerollCost).toBe(150); + game.doSelectModifier(); + await game.phaseInterceptor.to("SelectModifierPhase"); }, 20000); }); diff --git a/src/test/items/toxic_orb.test.ts b/src/test/items/toxic_orb.test.ts index 35d6e77b209..63c7b6245f5 100644 --- a/src/test/items/toxic_orb.test.ts +++ b/src/test/items/toxic_orb.test.ts @@ -1,15 +1,13 @@ import { StatusEffect } from "#app/data/status-effect"; -import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; -import { MessagePhase } from "#app/phases/message-phase"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; -import i18next, { initI18n } from "#app/plugins/i18n"; +import i18next from "#app/plugins/i18n"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +const TIMEOUT = 20 * 1000; describe("Items - Toxic orb", () => { let phaserGame: Phaser.Game; @@ -27,41 +25,36 @@ describe("Items - Toxic orb", () => { beforeEach(() => { game = new GameManager(phaserGame); - const moveToUse = Moves.GROWTH; - const oppMoveToUse = Moves.TACKLE; - game.override.battleType("single"); - game.override.enemySpecies(Species.RATTATA); - game.override.ability(Abilities.INSOMNIA); - game.override.enemyAbility(Abilities.INSOMNIA); - game.override.startingLevel(2000); - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset([ oppMoveToUse, oppMoveToUse, oppMoveToUse, oppMoveToUse ]); - game.override.startingHeldItems([{ - name: "TOXIC_ORB", - }]); + game.override + .battleType("single") + .enemySpecies(Species.RATTATA) + .ability(Abilities.BALL_FETCH) + .enemyAbility(Abilities.BALL_FETCH) + .moveset([ Moves.SPLASH ]) + .enemyMoveset(Moves.SPLASH) + .startingHeldItems([{ + name: "TOXIC_ORB", + }]); + + vi.spyOn(i18next, "t"); }); - it("TOXIC ORB", async () => { - initI18n(); - i18next.changeLanguage("en"); - const moveToUse = Moves.GROWTH; - await game.startBattle([ - Species.MIGHTYENA, - Species.MIGHTYENA, - ]); - expect(game.scene.modifiers[0].type.id).toBe("TOXIC_ORB"); + it("badly poisons the holder", async () => { + await game.classicMode.startBattle([ Species.MIGHTYENA ]); - game.move.select(moveToUse); + const player = game.scene.getPlayerField()[0]; - // will run the 13 phase from enemyCommandPhase to TurnEndPhase - await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); + game.move.select(Moves.SPLASH); + + await game.phaseInterceptor.to("TurnEndPhase"); // Toxic orb should trigger here - await game.phaseInterceptor.run(MessagePhase); - const message = game.textInterceptor.getLatestMessage(); - expect(message).toContain("statusEffect:toxic.obtainSource"); - await game.phaseInterceptor.run(MessagePhase); - const message2 = game.textInterceptor.getLatestMessage(); - expect(message2).toBe("statusEffect:toxic.activation"); - expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC); - }, 20000); + await game.phaseInterceptor.run("MessagePhase"); + expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything()); + + await game.toNextTurn(); + + expect(player.status?.effect).toBe(StatusEffect.TOXIC); + // Damage should not have ticked yet. + expect(player.status?.turnCount).toBe(0); + }, TIMEOUT); }); diff --git a/src/test/moves/aurora_veil.test.ts b/src/test/moves/aurora_veil.test.ts index e71d4ab9d11..243ba3a3269 100644 --- a/src/test/moves/aurora_veil.test.ts +++ b/src/test/moves/aurora_veil.test.ts @@ -111,7 +111,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (defender.scene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, move.category, defender.scene.currentBattle.double, multiplierHolder); + defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/destiny_bond.test.ts b/src/test/moves/destiny_bond.test.ts new file mode 100644 index 00000000000..4b4c8782862 --- /dev/null +++ b/src/test/moves/destiny_bond.test.ts @@ -0,0 +1,255 @@ +import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { allMoves } from "#app/data/move"; +import { Abilities } from "#enums/abilities"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { BattlerIndex } from "#app/battle"; +import { StatusEffect } from "#enums/status-effect"; +import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; + + +describe("Moves - Destiny Bond", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + const defaultParty = [ Species.BULBASAUR, Species.SQUIRTLE ]; + const enemyFirst = [ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]; + const playerFirst = [ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override.battleType("single") + .ability(Abilities.UNNERVE) // Pre-emptively prevent flakiness from opponent berries + .enemySpecies(Species.RATTATA) + .enemyAbility(Abilities.RUN_AWAY) + .startingLevel(100) // Make sure tested moves KO + .enemyLevel(5) + .enemyMoveset(Moves.DESTINY_BOND); + }); + + it("should KO the opponent on the same turn", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset(moveToUse); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + }); + + it("should KO the opponent on the next turn", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset([ Moves.SPLASH, moveToUse ]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + // Turn 1: Enemy uses Destiny Bond and doesn't faint + game.move.select(Moves.SPLASH); + await game.setTurnOrder(playerFirst); + await game.toNextTurn(); + + expect(enemyPokemon?.isFainted()).toBe(false); + expect(playerPokemon?.isFainted()).toBe(false); + + // Turn 2: Player KO's the enemy before the enemy's turn + game.move.select(moveToUse); + await game.setTurnOrder(playerFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + }); + + it("should fail if used twice in a row", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset([ Moves.SPLASH, moveToUse ]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + // Turn 1: Enemy uses Destiny Bond and doesn't faint + game.move.select(Moves.SPLASH); + await game.setTurnOrder(enemyFirst); + await game.toNextTurn(); + + expect(enemyPokemon?.isFainted()).toBe(false); + expect(playerPokemon?.isFainted()).toBe(false); + + // Turn 2: Enemy should fail Destiny Bond then get KO'd + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(false); + }); + + it("should not KO the opponent if the user dies to weather", async () => { + // Opponent will be reduced to 1 HP by False Swipe, then faint to Sandstorm + const moveToUse = Moves.FALSE_SWIPE; + + game.override.moveset(moveToUse) + .ability(Abilities.SAND_STREAM); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(false); + }); + + it("should not KO the opponent if the user had another turn", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset([ Moves.SPORE, moveToUse ]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + // Turn 1: Enemy uses Destiny Bond and doesn't faint + game.move.select(Moves.SPORE); + await game.setTurnOrder(enemyFirst); + await game.toNextTurn(); + + expect(enemyPokemon?.isFainted()).toBe(false); + expect(playerPokemon?.isFainted()).toBe(false); + expect(enemyPokemon?.status?.effect).toBe(StatusEffect.SLEEP); + + // Turn 2: Enemy should skip a turn due to sleep, then get KO'd + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(false); + }); + + it("should not KO an ally", async () => { + game.override.moveset([ Moves.DESTINY_BOND, Moves.CRUNCH ]) + .battleType("double"); + await game.classicMode.startBattle([ Species.SHEDINJA, Species.BULBASAUR, Species.SQUIRTLE ]); + + const enemyPokemon0 = game.scene.getEnemyField()[0]; + const enemyPokemon1 = game.scene.getEnemyField()[1]; + const playerPokemon0 = game.scene.getPlayerField()[0]; + const playerPokemon1 = game.scene.getPlayerField()[1]; + + // Shedinja uses Destiny Bond, then ally Bulbasaur KO's Shedinja with Crunch + game.move.select(Moves.DESTINY_BOND, 0); + game.move.select(Moves.CRUNCH, 1, BattlerIndex.PLAYER); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon0?.isFainted()).toBe(false); + expect(enemyPokemon1?.isFainted()).toBe(false); + expect(playerPokemon0?.isFainted()).toBe(true); + expect(playerPokemon1?.isFainted()).toBe(false); + }); + + it("should not cause a crash if the user is KO'd by Ceaseless Edge", async () => { + const moveToUse = Moves.CEASELESS_EDGE; + vi.spyOn(allMoves[moveToUse], "accuracy", "get").mockReturnValue(100); + + game.override.moveset(moveToUse); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + + // Ceaseless Edge spikes effect should still activate + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter.tagType).toBe(ArenaTagType.SPIKES); + expect(tagAfter.layers).toBe(1); + }); + + it("should not cause a crash if the user is KO'd by Pledge moves", async () => { + game.override.moveset([ Moves.GRASS_PLEDGE, Moves.WATER_PLEDGE ]) + .battleType("double"); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon0 = game.scene.getEnemyField()[0]; + const enemyPokemon1 = game.scene.getEnemyField()[1]; + const playerPokemon0 = game.scene.getPlayerField()[0]; + const playerPokemon1 = game.scene.getPlayerField()[1]; + + game.move.select(Moves.GRASS_PLEDGE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.WATER_PLEDGE, 1, BattlerIndex.ENEMY); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon0?.isFainted()).toBe(true); + expect(enemyPokemon1?.isFainted()).toBe(false); + expect(playerPokemon0?.isFainted()).toBe(false); + expect(playerPokemon1?.isFainted()).toBe(true); + + // Pledge secondary effect should still activate + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter.tagType).toBe(ArenaTagType.GRASS_WATER_PLEDGE); + }); + + /** + * In particular, this should prevent something like + * {@link https://github.com/pagefaultgames/pokerogue/issues/4219} + * from occurring with fainting by KO'ing a Destiny Bond user with U-Turn. + */ + it("should not allow the opponent to revive via Reviver Seed", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset(moveToUse) + .startingHeldItems([{ name: "REVIVER_SEED" }]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + + // Check that the Tackle user's Reviver Seed did not activate + const revSeeds = game.scene.getModifiers(PokemonInstantReviveModifier).filter(m => m.pokemonId === playerPokemon?.id); + expect(revSeeds.length).toBe(1); + }); +}); diff --git a/src/test/moves/dragon_tail.test.ts b/src/test/moves/dragon_tail.test.ts index eb02b09fbb4..cf801eb42c1 100644 --- a/src/test/moves/dragon_tail.test.ts +++ b/src/test/moves/dragon_tail.test.ts @@ -139,4 +139,58 @@ describe("Moves - Dragon Tail", () => { expect(enemy.isFullHp()).toBe(false); }); + + it("should force a switch upon fainting an opponent normally", async () => { + game.override.startingWave(5) + .startingLevel(1000); // To make sure Dragon Tail KO's the opponent + await game.classicMode.startBattle([ Species.DRATINI ]); + + game.move.select(Moves.DRAGON_TAIL); + + await game.toNextTurn(); + + // Make sure the enemy switched to a healthy Pokemon + const enemy = game.scene.getEnemyPokemon()!; + expect(enemy).toBeDefined(); + expect(enemy.isFullHp()).toBe(true); + + // Make sure the enemy has a fainted Pokemon in their party and not on the field + const faintedEnemy = game.scene.getEnemyParty().find(p => !p.isAllowedInBattle()); + expect(faintedEnemy).toBeDefined(); + expect(game.scene.getEnemyField().length).toBe(1); + }); + + it("should not cause a softlock when activating an opponent trainer's reviver seed", async () => { + game.override.startingWave(5) + .enemyHeldItems([{ name: "REVIVER_SEED" }]) + .startingLevel(1000); // To make sure Dragon Tail KO's the opponent + await game.classicMode.startBattle([ Species.DRATINI ]); + + game.move.select(Moves.DRAGON_TAIL); + + await game.toNextTurn(); + + // Make sure the enemy field is not empty and has a revived Pokemon + const enemy = game.scene.getEnemyPokemon()!; + expect(enemy).toBeDefined(); + expect(enemy.hp).toBe(Math.floor(enemy.getMaxHp() / 2)); + expect(game.scene.getEnemyField().length).toBe(1); + }); + + it("should not cause a softlock when activating a player's reviver seed", async () => { + game.override.startingHeldItems([{ name: "REVIVER_SEED" }]) + .enemyMoveset(Moves.DRAGON_TAIL) + .enemyLevel(1000); // To make sure Dragon Tail KO's the player + await game.classicMode.startBattle([ Species.DRATINI, Species.BULBASAUR ]); + + game.move.select(Moves.SPLASH); + + await game.toNextTurn(); + + // Make sure the player's field is not empty and has a revived Pokemon + const dratini = game.scene.getPlayerPokemon()!; + expect(dratini).toBeDefined(); + expect(dratini.hp).toBe(Math.floor(dratini.getMaxHp() / 2)); + expect(game.scene.getPlayerField().length).toBe(1); + }); }); diff --git a/src/test/moves/light_screen.test.ts b/src/test/moves/light_screen.test.ts index 2308458003d..11b8144bb4e 100644 --- a/src/test/moves/light_screen.test.ts +++ b/src/test/moves/light_screen.test.ts @@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (defender.scene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, move.category, defender.scene.currentBattle.double, multiplierHolder); + defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/obstruct.test.ts b/src/test/moves/obstruct.test.ts index fbb5437b43a..1649c199e32 100644 --- a/src/test/moves/obstruct.test.ts +++ b/src/test/moves/obstruct.test.ts @@ -1,6 +1,7 @@ -import { Moves } from "#app/enums/moves"; -import { Stat } from "#app/enums/stat"; import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -22,13 +23,15 @@ describe("Moves - Obstruct", () => { game = new GameManager(phaserGame); game.override .battleType("single") + .enemySpecies(Species.MAGIKARP) + .enemyMoveset(Moves.TACKLE) .enemyAbility(Abilities.BALL_FETCH) .ability(Abilities.BALL_FETCH) - .moveset([ Moves.OBSTRUCT ]); + .moveset([ Moves.OBSTRUCT ]) + .starterSpecies(Species.FEEBAS); }); it("protects from contact damaging moves and lowers the opponent's defense by 2 stages", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH)); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -42,7 +45,6 @@ describe("Moves - Obstruct", () => { }); it("bypasses accuracy checks when applying protection and defense reduction", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH)); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -59,7 +61,7 @@ describe("Moves - Obstruct", () => { ); it("protects from non-contact damaging moves and doesn't lower the opponent's defense by 2 stages", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.WATER_GUN)); + game.override.enemyMoveset(Moves.WATER_GUN); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -73,7 +75,7 @@ describe("Moves - Obstruct", () => { }); it("doesn't protect from status moves", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.GROWL)); + game.override.enemyMoveset(Moves.GROWL); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -83,4 +85,14 @@ describe("Moves - Obstruct", () => { expect(player.getStatStage(Stat.ATK)).toBe(-1); }); + + it("doesn't reduce the stats of an opponent with Clear Body/etc", async () => { + game.override.enemyAbility(Abilities.CLEAR_BODY); + await game.classicMode.startBattle(); + + game.move.select(Moves.OBSTRUCT); + await game.phaseInterceptor.to("BerryPhase"); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(0); + }); }); diff --git a/src/test/moves/power_trick.test.ts b/src/test/moves/power_trick.test.ts new file mode 100644 index 00000000000..a064a43dec4 --- /dev/null +++ b/src/test/moves/power_trick.test.ts @@ -0,0 +1,113 @@ +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import { Moves } from "#enums/moves"; +import { Stat } from "#enums/stat"; +import { Species } from "#enums/species"; +import { TurnEndPhase } from "#app/phases/turn-end-phase"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; + +describe("Moves - Power Trick", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .enemySpecies(Species.MEW) + .enemyLevel(200) + .moveset([ Moves.POWER_TRICK ]) + .ability(Abilities.BALL_FETCH); + }); + + it("swaps the user's ATK and DEF stats", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE ]); + + const player = game.scene.getPlayerPokemon()!; + const baseATK = player.getStat(Stat.ATK, false); + const baseDEF = player.getStat(Stat.DEF, false); + + game.move.select(Moves.POWER_TRICK); + + await game.phaseInterceptor.to(TurnEndPhase); + + expect(player.getStat(Stat.ATK, false)).toBe(baseDEF); + expect(player.getStat(Stat.DEF, false)).toBe(baseATK); + expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeDefined(); + }); + + it("resets initial ATK and DEF stat swap when used consecutively", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE ]); + + const player = game.scene.getPlayerPokemon()!; + const baseATK = player.getStat(Stat.ATK, false); + const baseDEF = player.getStat(Stat.DEF, false); + + game.move.select(Moves.POWER_TRICK); + + await game.phaseInterceptor.to(TurnEndPhase); + + game.move.select(Moves.POWER_TRICK); + + await game.phaseInterceptor.to(TurnEndPhase); + + expect(player.getStat(Stat.ATK, false)).toBe(baseATK); + expect(player.getStat(Stat.DEF, false)).toBe(baseDEF); + expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined(); + }); + + it("should pass effect when using BATON_PASS", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]); + await game.override.moveset([ Moves.POWER_TRICK, Moves.BATON_PASS ]); + + const player = game.scene.getPlayerPokemon()!; + player.addTag(BattlerTagType.POWER_TRICK); + + game.move.select(Moves.BATON_PASS); + game.doSelectPartyPokemon(1); + + await game.phaseInterceptor.to(TurnEndPhase); + + const switchedPlayer = game.scene.getPlayerPokemon()!; + const baseATK = switchedPlayer.getStat(Stat.ATK); + const baseDEF = switchedPlayer.getStat(Stat.DEF); + + expect(switchedPlayer.getStat(Stat.ATK, false)).toBe(baseDEF); + expect(switchedPlayer.getStat(Stat.DEF, false)).toBe(baseATK); + expect(switchedPlayer.getTag(BattlerTagType.POWER_TRICK)).toBeDefined(); + }); + + it("should remove effect after using Transform", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]); + await game.override.moveset([ Moves.POWER_TRICK, Moves.TRANSFORM ]); + + const player = game.scene.getPlayerPokemon()!; + player.addTag(BattlerTagType.POWER_TRICK); + + game.move.select(Moves.TRANSFORM); + + await game.phaseInterceptor.to(TurnEndPhase); + + const enemy = game.scene.getEnemyPokemon()!; + const baseATK = enemy.getStat(Stat.ATK); + const baseDEF = enemy.getStat(Stat.DEF); + + expect(player.getStat(Stat.ATK, false)).toBe(baseATK); + expect(player.getStat(Stat.DEF, false)).toBe(baseDEF); + expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined(); + }); +}); diff --git a/src/test/moves/reflect.test.ts b/src/test/moves/reflect.test.ts index 41a10988552..b18b2423895 100644 --- a/src/test/moves/reflect.test.ts +++ b/src/test/moves/reflect.test.ts @@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (defender.scene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, move.category, defender.scene.currentBattle.double, multiplierHolder); + defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/safeguard.test.ts b/src/test/moves/safeguard.test.ts index c180ff338a5..6505162fd04 100644 --- a/src/test/moves/safeguard.test.ts +++ b/src/test/moves/safeguard.test.ts @@ -33,7 +33,7 @@ describe("Moves - Safeguard", () => { .enemyLevel(5) .starterSpecies(Species.DRATINI) .moveset([ Moves.NUZZLE, Moves.SPORE, Moves.YAWN, Moves.SPLASH ]) - .ability(Abilities.BALL_FETCH); + .ability(Abilities.UNNERVE); // Stop wild Pokemon from potentially eating Lum Berry }); it("protects from damaging moves with additional effects", async () => { diff --git a/src/test/moves/scale_shot.test.ts b/src/test/moves/scale_shot.test.ts index 2730d05306d..e4d768fa13a 100644 --- a/src/test/moves/scale_shot.test.ts +++ b/src/test/moves/scale_shot.test.ts @@ -1,3 +1,5 @@ +import { BattlerIndex } from "#app/battle"; +import { allMoves } from "#app/data/move"; import { DamagePhase } from "#app/phases/damage-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; @@ -8,7 +10,7 @@ import { Species } from "#enums/species"; import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; describe("Moves - Scale Shot", () => { let phaserGame: Phaser.Game; @@ -30,45 +32,54 @@ describe("Moves - Scale Shot", () => { .moveset([ Moves.SCALE_SHOT ]) .battleType("single") .disableCrits() - .starterSpecies(Species.MINCCINO) .ability(Abilities.NO_GUARD) .passiveAbility(Abilities.SKILL_LINK) - .enemyAbility(Abilities.SHEER_FORCE) - .enemyPassiveAbility(Abilities.STALL) - .enemyMoveset(Moves.SKILL_SWAP) - .enemyLevel(5); + .enemyMoveset(Moves.SPLASH) + .enemyLevel(3); }); it("applies stat changes after last hit", async () => { - await game.classicMode.startBattle([ Species.FORRETRESS ]); + game.override.enemySpecies(Species.FORRETRESS); + + await game.classicMode.startBattle([ Species.MINCCINO ]); const minccino = game.scene.getPlayerPokemon()!; game.move.select(Moves.SCALE_SHOT); + + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to(MoveEffectPhase); await game.phaseInterceptor.to(DamagePhase); + + //check that stats haven't changed after one or two hits have occurred await game.phaseInterceptor.to(MoveEffectPhase); - expect (minccino?.getStatStage(Stat.DEF)).toBe(0); - expect (minccino?.getStatStage(Stat.SPD)).toBe(0); + expect(minccino.getStatStage(Stat.DEF)).toBe(0); + expect(minccino.getStatStage(Stat.SPD)).toBe(0); + + //check that stats changed on last hit await game.phaseInterceptor.to(MoveEndPhase); - expect (minccino.getStatStage(Stat.DEF)).toBe(-1); - expect (minccino.getStatStage(Stat.SPD)).toBe(1); + expect(minccino.getStatStage(Stat.DEF)).toBe(-1); + expect(minccino.getStatStage(Stat.SPD)).toBe(1); }); it("unaffected by sheer force", async () => { - await game.classicMode.startBattle([ Species.WOBBUFFET ]); + const moveToCheck = allMoves[Moves.SCALE_SHOT]; + const basePower = moveToCheck.power; + + game.override.enemySpecies(Species.WOBBUFFET); + + vi.spyOn(moveToCheck, "calculateBattlePower"); + + await game.classicMode.startBattle([ Species.MINCCINO ]); const minccino = game.scene.getPlayerPokemon()!; - const wobbuffet = game.scene.getEnemyPokemon()!; - wobbuffet.setStat(Stat.HP, 100, true); - wobbuffet.hp = 100; + game.move.select(Moves.SCALE_SHOT); await game.phaseInterceptor.to(TurnEndPhase); - const hpafter1 = wobbuffet.hp; + //effect not nullified by sheer force - expect (minccino.getStatStage(Stat.DEF)).toBe(-1); - expect (minccino.getStatStage(Stat.SPD)).toBe(1); - game.move.select(Moves.SCALE_SHOT); - await game.phaseInterceptor.to(MoveEndPhase); - const hpafter2 = wobbuffet.hp; - //check damage not boosted- make damage before sheer force a little lower than theoretical boosted sheer force damage - expect (100 - hpafter1).toBe(hpafter1 - hpafter2); + expect(minccino.getStatStage(Stat.DEF)).toBe(-1); + expect(minccino.getStatStage(Stat.SPD)).toBe(1); + + //power not boosted by sheer force + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower); }); }); diff --git a/src/test/moves/secret_power.test.ts b/src/test/moves/secret_power.test.ts new file mode 100644 index 00000000000..ff0b5ae8c24 --- /dev/null +++ b/src/test/moves/secret_power.test.ts @@ -0,0 +1,89 @@ +import { Abilities } from "#enums/abilities"; +import { Biome } from "#enums/biome"; +import { Moves } from "#enums/moves"; +import { Stat } from "#enums/stat"; +import { allMoves, SecretPowerAttr } from "#app/data/move"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { StatusEffect } from "#enums/status-effect"; +import { BattlerIndex } from "#app/battle"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { ArenaTagSide } from "#app/data/arena-tag"; + +describe("Moves - Secret Power", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SECRET_POWER ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyLevel(60) + .enemyAbility(Abilities.BALL_FETCH); + }); + + it("Secret Power checks for an active terrain first then looks at the biome for its secondary effect", async () => { + game.override + .startingBiome(Biome.VOLCANO) + .enemyMoveset([ Moves.SPLASH, Moves.MISTY_TERRAIN ]); + vi.spyOn(allMoves[Moves.SECRET_POWER], "chance", "get").mockReturnValue(100); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + // No Terrain + Biome.VOLCANO --> Burn + game.move.select(Moves.SECRET_POWER); + await game.forceEnemyMove(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyPokemon.status?.effect).toBe(StatusEffect.BURN); + + // Misty Terrain --> SpAtk -1 + game.move.select(Moves.SECRET_POWER); + await game.forceEnemyMove(Moves.MISTY_TERRAIN); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-1); + }); + + it("the 'rainbow' effect of fire+water pledge does not double the chance of secret power's secondary effect", + async () => { + game.override + .moveset([ Moves.FIRE_PLEDGE, Moves.WATER_PLEDGE, Moves.SECRET_POWER, Moves.SPLASH ]) + .enemyMoveset([ Moves.SPLASH ]) + .battleType("double"); + await game.classicMode.startBattle([ Species.BLASTOISE, Species.CHARIZARD ]); + + const secretPowerAttr = allMoves[Moves.SECRET_POWER].getAttrs(SecretPowerAttr)[0]; + vi.spyOn(secretPowerAttr, "getMoveChance"); + + game.move.select(Moves.WATER_PLEDGE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.FIRE_PLEDGE, 1, BattlerIndex.ENEMY_2); + + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.arena.getTagOnSide(ArenaTagType.WATER_FIRE_PLEDGE, ArenaTagSide.PLAYER)).toBeDefined(); + + game.move.select(Moves.SECRET_POWER, 0, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, 1); + + await game.phaseInterceptor.to("BerryPhase", false); + + expect(secretPowerAttr.getMoveChance).toHaveLastReturnedWith(30); + } + ); +}); diff --git a/src/test/moves/sketch.test.ts b/src/test/moves/sketch.test.ts new file mode 100644 index 00000000000..2e3eb97a76c --- /dev/null +++ b/src/test/moves/sketch.test.ts @@ -0,0 +1,53 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { MoveResult } from "#app/field/pokemon"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Sketch", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.SHUCKLE) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("Sketch should not fail even if a previous Sketch failed to retrieve a valid move and ran out of PP", async () => { + game.override.moveset([ Moves.SKETCH, Moves.SKETCH ]); + + await game.classicMode.startBattle([ Species.REGIELEKI ]); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(Moves.SKETCH); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + const moveSlot0 = playerPokemon?.getMoveset()[0]; + expect(moveSlot0?.moveId).toBe(Moves.SKETCH); + expect(moveSlot0?.getPpRatio()).toBe(0); + + await game.toNextTurn(); + game.move.select(Moves.SKETCH); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + // Can't verify if the player Pokemon's moveset was successfully changed because of overrides. + }); +}); diff --git a/src/test/moves/syrup_bomb.test.ts b/src/test/moves/syrup_bomb.test.ts index 7f914e45cc6..ea2f8b6bab3 100644 --- a/src/test/moves/syrup_bomb.test.ts +++ b/src/test/moves/syrup_bomb.test.ts @@ -1,4 +1,3 @@ -import { allMoves } from "#app/data/move"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; @@ -7,7 +6,7 @@ import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { BattlerIndex } from "#app/battle"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Moves - SYRUP BOMB", () => { let phaserGame: Phaser.Game; @@ -26,20 +25,21 @@ describe("Moves - SYRUP BOMB", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .starterSpecies(Species.MAGIKARP) + .battleType("single") .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.BALL_FETCH) + .ability(Abilities.BALL_FETCH) .startingLevel(30) .enemyLevel(100) .moveset([ Moves.SYRUP_BOMB, Moves.SPLASH ]) .enemyMoveset(Moves.SPLASH); - vi.spyOn(allMoves[Moves.SYRUP_BOMB], "accuracy", "get").mockReturnValue(100); }); //Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/syrup_bomb_(move) it("decreases the target Pokemon's speed stat once per turn for 3 turns", async () => { - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); const targetPokemon = game.scene.getEnemyPokemon()!; expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0); @@ -66,7 +66,7 @@ describe("Moves - SYRUP BOMB", () => { it("does not affect Pokemon with the ability Bulletproof", async () => { game.override.enemyAbility(Abilities.BULLETPROOF); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); const targetPokemon = game.scene.getEnemyPokemon()!; @@ -79,4 +79,18 @@ describe("Moves - SYRUP BOMB", () => { expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0); } ); + + it("stops lowering the target's speed if the user leaves the field", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]); + + game.move.select(Moves.SYRUP_BOMB); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.move.forceHit(); + await game.toNextTurn(); + + game.doSwitchPokemon(1); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPD)).toBe(-1); + }); }); diff --git a/src/test/moves/telekinesis.test.ts b/src/test/moves/telekinesis.test.ts new file mode 100644 index 00000000000..76c0d001f00 --- /dev/null +++ b/src/test/moves/telekinesis.test.ts @@ -0,0 +1,124 @@ +import { BattlerTagType } from "#enums/battler-tag-type"; +import { allMoves } from "#app/data/move"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { MoveResult } from "#app/field/pokemon"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; + +describe("Moves - Telekinesis", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.TELEKINESIS, Moves.TACKLE, Moves.MUD_SHOT, Moves.SMACK_DOWN ]) + .battleType("single") + .enemySpecies(Species.SNORLAX) + .enemyLevel(60) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([ Moves.SPLASH ]); + }); + + it("Telekinesis makes the affected vulnerable to most attacking moves regardless of accuracy", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const enemyOpponent = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.TELEKINESIS); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined(); + + await game.toNextTurn(); + vi.spyOn(allMoves[Moves.TACKLE], "accuracy", "get").mockReturnValue(0); + game.move.select(Moves.TACKLE); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.isFullHp()).toBe(false); + }); + + it("Telekinesis makes the affected airborne and immune to most Ground-moves", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const enemyOpponent = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.TELEKINESIS); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined(); + + await game.toNextTurn(); + vi.spyOn(allMoves[Moves.MUD_SHOT], "accuracy", "get").mockReturnValue(100); + game.move.select(Moves.MUD_SHOT); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.isFullHp()).toBe(true); + }); + + it("Telekinesis can still affect Pokemon that have been transformed into invalid Pokemon", async () => { + game.override.enemyMoveset(Moves.TRANSFORM); + await game.classicMode.startBattle([ Species.DIGLETT ]); + + const enemyOpponent = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.TELEKINESIS); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined(); + expect(enemyOpponent.summonData.speciesForm?.speciesId).toBe(Species.DIGLETT); + }); + + it("Moves like Smack Down and 1000 Arrows remove all effects of Telekinesis from the target Pokemon", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const enemyOpponent = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.TELEKINESIS); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined(); + + await game.toNextTurn(); + game.move.select(Moves.SMACK_DOWN); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeUndefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeUndefined(); + }); + + it("Ingrain will remove the floating effect of Telekinesis, but not the 100% hit", async () => { + game.override.enemyMoveset([ Moves.SPLASH, Moves.INGRAIN ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyOpponent = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.TELEKINESIS); + await game.forceEnemyMove(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined(); + + await game.toNextTurn(); + vi.spyOn(allMoves[Moves.MUD_SHOT], "accuracy", "get").mockReturnValue(0); + game.move.select(Moves.MUD_SHOT); + await game.forceEnemyMove(Moves.INGRAIN); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.INGRAIN)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeUndefined(); + expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + }); +}); diff --git a/src/test/moves/thousand_arrows.test.ts b/src/test/moves/thousand_arrows.test.ts index 112be476955..976b4352ee4 100644 --- a/src/test/moves/thousand_arrows.test.ts +++ b/src/test/moves/thousand_arrows.test.ts @@ -85,13 +85,13 @@ describe("Moves - Thousand Arrows", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; - enemyPokemon.addTag(BattlerTagType.MAGNET_RISEN, undefined, Moves.MAGNET_RISE); + enemyPokemon.addTag(BattlerTagType.FLOATING, undefined, Moves.MAGNET_RISE); game.move.select(Moves.THOUSAND_ARROWS); await game.phaseInterceptor.to(BerryPhase, false); - expect(enemyPokemon.getTag(BattlerTagType.MAGNET_RISEN)).toBeUndefined(); + expect(enemyPokemon.getTag(BattlerTagType.FLOATING)).toBeUndefined(); expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); } diff --git a/src/test/moves/toxic_spikes.test.ts b/src/test/moves/toxic_spikes.test.ts new file mode 100644 index 00000000000..a5c63a2652f --- /dev/null +++ b/src/test/moves/toxic_spikes.test.ts @@ -0,0 +1,139 @@ +import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { StatusEffect } from "#app/data/status-effect"; +import { decrypt, encrypt, GameData, SessionSaveData } from "#app/system/game-data"; +import { Abilities } from "#enums/abilities"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Toxic Spikes", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .startingWave(5) + .enemySpecies(Species.RATTATA) + .enemyAbility(Abilities.BALL_FETCH) + .ability(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR, Moves.COURT_CHANGE ]); + }); + + it("should not affect the opponent if they do not switch", async () => { + await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]); + + const enemy = game.scene.getEnemyField()[0]; + + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + game.doSwitchPokemon(1); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(enemy.hp).toBe(enemy.getMaxHp()); + expect(enemy.status?.effect).toBeUndefined(); + }); + + it("should poison the opponent if they switch into 1 layer", async () => { + await game.classicMode.runToSummon([ Species.MIGHTYENA ]); + + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.ROAR); + await game.phaseInterceptor.to("TurnEndPhase"); + + const enemy = game.scene.getEnemyField()[0]; + + expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); + expect(enemy.status?.effect).toBe(StatusEffect.POISON); + }); + + it("should badly poison the opponent if they switch into 2 layers", async () => { + await game.classicMode.runToSummon([ Species.MIGHTYENA ]); + + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.ROAR); + await game.phaseInterceptor.to("TurnEndPhase"); + + const enemy = game.scene.getEnemyField()[0]; + expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); + expect(enemy.status?.effect).toBe(StatusEffect.TOXIC); + }); + + it("should be removed if a grounded poison pokemon switches in", async () => { + await game.classicMode.runToSummon([ Species.MUK, Species.PIDGEY ]); + + const muk = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.TOXIC_SPIKES); + await game.toNextTurn(); + // also make sure the toxic spikes are removed even if the pokemon + // that set them up is the one switching in (https://github.com/pagefaultgames/pokerogue/issues/935) + game.move.select(Moves.COURT_CHANGE); + await game.toNextTurn(); + game.doSwitchPokemon(1); + await game.toNextTurn(); + game.doSwitchPokemon(1); + await game.toNextTurn(); + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(muk.isFullHp()).toBe(true); + expect(muk.status?.effect).toBeUndefined(); + expect(game.scene.arena.tags.length).toBe(0); + }); + + it("shouldn't create multiple layers per use in doubles", async () => { + await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]); + + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + + const arenaTags = (game.scene.arena.getTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag); + expect(arenaTags.tagType).toBe(ArenaTagType.TOXIC_SPIKES); + expect(arenaTags.layers).toBe(1); + }); + + it("should persist through reload", async () => { + game.override.startingWave(1); + const scene = game.scene; + const gameData = new GameData(scene); + + await game.classicMode.runToSummon([ Species.MIGHTYENA ]); + + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to("BattleEndPhase"); + await game.toNextWave(); + + const sessionData : SessionSaveData = gameData["getSessionSaveData"](game.scene); + localStorage.setItem("sessionTestData", encrypt(JSON.stringify(sessionData), true)); + const recoveredData : SessionSaveData = gameData.parseSessionData(decrypt(localStorage.getItem("sessionTestData")!, true)); + gameData.loadSession(game.scene, 0, recoveredData); + + expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags); + localStorage.removeItem("sessionTestData"); + }); +}); diff --git a/src/test/moves/triple_arrows.test.ts b/src/test/moves/triple_arrows.test.ts new file mode 100644 index 00000000000..98ad29997df --- /dev/null +++ b/src/test/moves/triple_arrows.test.ts @@ -0,0 +1,60 @@ +import { allMoves, FlinchAttr, StatStageChangeAttr } from "#app/data/move"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Triple Arrows", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + const tripleArrows = allMoves[Moves.TRIPLE_ARROWS]; + const flinchAttr = tripleArrows.getAttrs(FlinchAttr)[0]; + const defDropAttr = tripleArrows.getAttrs(StatStageChangeAttr)[0]; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(Abilities.BALL_FETCH) + .moveset([ Moves.TRIPLE_ARROWS ]) + .battleType("single") + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.STURDY) + .enemyMoveset(Moves.SPLASH); + + vi.spyOn(flinchAttr, "getMoveChance"); + vi.spyOn(defDropAttr, "getMoveChance"); + }); + + it("has a 30% flinch chance and 50% defense drop chance", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.TRIPLE_ARROWS); + await game.phaseInterceptor.to("BerryPhase"); + + expect(flinchAttr.getMoveChance).toHaveReturnedWith(30); + expect(defDropAttr.getMoveChance).toHaveReturnedWith(50); + }); + + it("is affected normally by Serene Grace", async () => { + game.override.ability(Abilities.SERENE_GRACE); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.TRIPLE_ARROWS); + await game.phaseInterceptor.to("BerryPhase"); + + expect(flinchAttr.getMoveChance).toHaveReturnedWith(60); + expect(defDropAttr.getMoveChance).toHaveReturnedWith(100); + }); +}); diff --git a/src/test/moves/u_turn.test.ts b/src/test/moves/u_turn.test.ts index 17e02cb50ef..b995c20f503 100644 --- a/src/test/moves/u_turn.test.ts +++ b/src/test/moves/u_turn.test.ts @@ -96,4 +96,23 @@ describe("Moves - U-turn", () => { expect(game.scene.getEnemyPokemon()!.battleData.abilityRevealed).toBe(true); // proxy for asserting ability activated expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase"); }, 20000); + + it("still forces a switch if u-turn KO's the opponent", async () => { + game.override.startingLevel(1000); // Ensure that U-Turn KO's the opponent + await game.classicMode.startBattle([ + Species.RAICHU, + Species.SHUCKLE + ]); + const enemy = game.scene.getEnemyPokemon()!; + + // KO the opponent with U-Turn + game.move.select(Moves.U_TURN); + game.doSelectPartyPokemon(1); + await game.phaseInterceptor.to(TurnEndPhase); + expect(enemy.isFainted()).toBe(true); + + // Check that U-Turn forced a switch + expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase"); + expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.SHUCKLE); + }); }); diff --git a/src/test/mystery-encounter/encounter-test-utils.ts b/src/test/mystery-encounter/encounter-test-utils.ts index 9fb2504c02b..f95a442d4c2 100644 --- a/src/test/mystery-encounter/encounter-test-utils.ts +++ b/src/test/mystery-encounter/encounter-test-utils.ts @@ -97,20 +97,20 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN uiHandler.unblockInput(); // input are blocked by 1s to prevent accidental input. Tests need to handle that switch (optionNo) { - default: - case 1: + default: + case 1: // no movement needed. Default cursor position - break; - case 2: - uiHandler.processInput(Button.RIGHT); - break; - case 3: - uiHandler.processInput(Button.DOWN); - break; - case 4: - uiHandler.processInput(Button.RIGHT); - uiHandler.processInput(Button.DOWN); - break; + break; + case 2: + uiHandler.processInput(Button.RIGHT); + break; + case 3: + uiHandler.processInput(Button.DOWN); + break; + case 4: + uiHandler.processInput(Button.RIGHT); + uiHandler.processInput(Button.DOWN); + break; } if (!isNullOrUndefined(secondaryOptionSelect?.pokemonNo)) { diff --git a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index f24800eaa71..7d783958422 100644 --- a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -16,6 +16,7 @@ import { EggTier } from "#enums/egg-type"; import { CommandPhase } from "#app/phases/command-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/aTrainersTest"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -106,7 +107,8 @@ describe("A Trainer's Test - Mystery Encounter", () => { expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(scene.currentBattle.trainer).toBeDefined(); - expect([ "trainerNames:buck", "trainerNames:cheryl", "trainerNames:marley", "trainerNames:mira", "trainerNames:riley" ].includes(scene.currentBattle.trainer!.config.name)).toBeTruthy(); + expect([ i18next.t("trainerNames:buck"), i18next.t("trainerNames:cheryl"), i18next.t("trainerNames:marley"), i18next.t("trainerNames:mira"), i18next.t("trainerNames:riley") ] + .map(name => name.toLowerCase()).includes(scene.currentBattle.trainer!.config.name)).toBeTruthy(); expect(enemyField[0]).toBeDefined(); }); @@ -126,7 +128,7 @@ describe("A Trainer's Test - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + 1).toBe(eggsAfter.length); const eggTier = eggsAfter[eggsAfter.length - 1].tier; - expect(eggTier === EggTier.ULTRA || eggTier === EggTier.MASTER).toBeTruthy(); + expect(eggTier === EggTier.EPIC || eggTier === EggTier.LEGENDARY).toBeTruthy(); }); }); @@ -174,7 +176,7 @@ describe("A Trainer's Test - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + 1).toBe(eggsAfter.length); const eggTier = eggsAfter[eggsAfter.length - 1].tier; - expect(eggTier).toBe(EggTier.GREAT); + expect(eggTier).toBe(EggTier.RARE); }); it("should leave encounter without battle", async () => { diff --git a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index 99a835cb6ae..a72a9fbb5a3 100644 --- a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -16,6 +16,7 @@ import { Moves } from "#enums/moves"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/absoluteAvarice"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -146,7 +147,7 @@ describe("Absolute Avarice - Mystery Encounter", () => { const pokemonId = partyPokemon.id; const pokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemonId, true) as PokemonHeldItemModifier[]; - const revSeed = pokemonItems.find(i => i.type.name === "modifierType:ModifierType.REVIVER_SEED.name"); + const revSeed = pokemonItems.find(i => i.type.name === i18next.t("modifierType:ModifierType.REVIVER_SEED.name")); expect(revSeed).toBeDefined; expect(revSeed?.stackCount).toBe(1); } diff --git a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 77d5a842b47..0585b4ce72b 100644 --- a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -17,6 +17,8 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Moves } from "#enums/moves"; import { ShinyRateBoosterModifier } from "#app/modifier/modifier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import i18next from "i18next"; +import { Abilities } from "#enums/abilities"; const namespace = "mysteryEncounters/anOfferYouCantRefuse"; /** Gyarados for Indimidate */ @@ -36,10 +38,11 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override.mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves() + .ability(Abilities.INTIMIDATE); // Extortion ability const biomeMap = new Map([ [ Biome.VOLCANO, [ MysteryEncounterType.MYSTERIOUS_CHALLENGERS ]], @@ -93,8 +96,8 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.strongestPokemon).toBeDefined(); expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.price).toBeDefined(); - expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe("ability:intimidate.name"); - expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe("ability:intimidate.name"); + expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe(i18next.t("ability:intimidate.name")); + expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe(i18next.t("ability:intimidate.name")); expect(AnOfferYouCantRefuseEncounter.misc.pokemon instanceof PlayerPokemon).toBeTruthy(); expect(AnOfferYouCantRefuseEncounter.misc?.price?.toString()).toBe(AnOfferYouCantRefuseEncounter.dialogueTokens?.price); expect(onInitResult).toBe(true); @@ -194,6 +197,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { }); it("should award EXP to a pokemon with a move in EXTORTION_MOVES", async () => { + game.override.ability(Abilities.SYNCHRONIZE); // Not an extortion ability, so we can test extortion move await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, [ Species.ABRA ]); const party = scene.getParty(); const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA)!; diff --git a/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index 8e286468bea..bfa3d428bc0 100644 --- a/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -176,10 +176,12 @@ describe("Berries Abound - Mystery Encounter", () => { const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText"); await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty); + scene.getParty().forEach(pkm => { + vi.spyOn(pkm, "getStat").mockReturnValue(1); // for ease return for every stat + }); + const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId; - // Setting enemy's level arbitrarily high to outspeed - config.pokemonConfigs![0].dataSource!.level = 1000; await runMysteryEncounterToEnd(game, 2, undefined, true); @@ -198,10 +200,12 @@ describe("Berries Abound - Mystery Encounter", () => { const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText"); await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty); + scene.getParty().forEach(pkm => { + vi.spyOn(pkm, "getStat").mockReturnValue(1); // for ease return for every stat + }); + const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId; - // Setting enemy's level arbitrarily high to outspeed - config.pokemonConfigs![0].dataSource!.level = 1000; await runMysteryEncounterToEnd(game, 2, undefined, true); diff --git a/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 0dc02e03c6d..e0f37c7e045 100644 --- a/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -476,10 +476,11 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); + expect(modifierSelectHandler.options.length).toEqual(4); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MASTER_BALL"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MAX_LURE"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toBe("FORM_CHANGE_ITEM"); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MEGA_BRACELET"); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toBe("DYNAMAX_BAND"); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toBe("FORM_CHANGE_ITEM"); }); it("should leave encounter without battle", async () => { diff --git a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index b92a4d96adc..82ed558e6db 100644 --- a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -118,11 +118,11 @@ describe("Clowning Around - Mystery Encounter", () => { }); expect(config.pokemonConfigs?.[1]).toEqual({ species: getPokemonSpecies(Species.BLACEPHALON), - mysteryEncounterPokemonData: expect.anything(), + customPokemonData: expect.anything(), isBoss: true, moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ] }); - expect(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.types.length).toBe(2); + expect(config.pokemonConfigs?.[1].customPokemonData?.types.length).toBe(2); expect([ Abilities.STURDY, Abilities.PICKUP, @@ -139,8 +139,8 @@ describe("Clowning Around - Mystery Encounter", () => { Abilities.MAGICIAN, Abilities.SHEER_FORCE, Abilities.PRANKSTER - ]).toContain(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.ability); - expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.ability); + ]).toContain(config.pokemonConfigs?.[1].customPokemonData?.ability); + expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs?.[1].customPokemonData?.ability); await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled()); await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled()); expect(onInitResult).toBe(true); @@ -219,7 +219,7 @@ describe("Clowning Around - Mystery Encounter", () => { await game.phaseInterceptor.to(NewBattlePhase, false); const leadPokemon = scene.getParty()[0]; - expect(leadPokemon.mysteryEncounterPokemonData?.ability).toBe(abilityToTrain); + expect(leadPokemon.customPokemonData?.ability).toBe(abilityToTrain); }); }); @@ -340,9 +340,9 @@ describe("Clowning Around - Mystery Encounter", () => { scene.getParty()[2].moveset = []; await runMysteryEncounterToEnd(game, 3); - const leadTypesAfter = scene.getParty()[0].mysteryEncounterPokemonData?.types; - const secondaryTypesAfter = scene.getParty()[1].mysteryEncounterPokemonData?.types; - const thirdTypesAfter = scene.getParty()[2].mysteryEncounterPokemonData?.types; + const leadTypesAfter = scene.getParty()[0].customPokemonData?.types; + const secondaryTypesAfter = scene.getParty()[1].customPokemonData?.types; + const thirdTypesAfter = scene.getParty()[2].customPokemonData?.types; expect(leadTypesAfter.length).toBe(2); expect(leadTypesAfter[0]).toBe(Type.WATER); diff --git a/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts b/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts index 232bad3c2b8..a6f925274c3 100644 --- a/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts @@ -103,11 +103,11 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_attack"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_defense"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.DIRE_HIT.name"); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_attack")); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_defense")); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed")); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.DIRE_HIT.name")); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name")); }); it("should leave encounter without battle", async () => { @@ -150,11 +150,11 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_sp_atk"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_sp_def"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.DIRE_HIT.name"); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_sp_atk")); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_sp_def")); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed")); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.DIRE_HIT.name")); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name")); }); it("should leave encounter without battle", async () => { @@ -198,12 +198,12 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_accuracy"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:ModifierType.AddPokeballModifierType.name"); - expect(i18next.t).toHaveBeenCalledWith("modifierType:ModifierType.AddPokeballModifierType.name", expect.objectContaining({ modifierCount: 5 })); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.IV_SCANNER.name"); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_accuracy")); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed")); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { modifierCount: 5, pokeballName: i18next.t("pokeball:greatBall") })); + expect(i18next.t).toHaveBeenCalledWith(("modifierType:ModifierType.AddPokeballModifierType.name"), expect.objectContaining({ modifierCount: 5 })); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.IV_SCANNER.name")); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name")); }); it("should leave encounter without battle", async () => { diff --git a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 3d2533c0817..a4f303d121f 100644 --- a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -22,6 +22,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/fieryFallout"; /** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */ @@ -205,7 +206,7 @@ describe("Fiery Fallout - Mystery Encounter", () => { const burnablePokemon = party.filter((pkm) => pkm.isAllowedInBattle() && !pkm.getTypes().includes(Type.FIRE)); const notBurnablePokemon = party.filter((pkm) => !pkm.isAllowedInBattle() || pkm.getTypes().includes(Type.FIRE)); - expect(scene.currentBattle.mysteryEncounter?.dialogueTokens["burnedPokemon"]).toBe("pokemon:gengar"); + expect(scene.currentBattle.mysteryEncounter?.dialogueTokens["burnedPokemon"]).toBe(i18next.t("pokemon:gengar")); burnablePokemon.forEach((pkm) => { expect(pkm.hp, `${pkm.name} should have received 20% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.2)); }); diff --git a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts index 456c18c572d..dec14d46cc8 100644 --- a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts @@ -14,6 +14,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import BattleScene from "#app/battle-scene"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/lostAtSea"; @@ -86,8 +87,8 @@ describe("Lost at Sea - Mystery Encounter", () => { const onInitResult = onInit!(scene); expect(LostAtSeaEncounter.dialogueTokens?.damagePercentage).toBe("25"); - expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe("move:surf.name"); - expect(LostAtSeaEncounter.dialogueTokens?.option2RequiredMove).toBe("move:fly.name"); + expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe(i18next.t("move:surf.name")); + expect(LostAtSeaEncounter.dialogueTokens?.option2RequiredMove).toBe(i18next.t("move:fly.name")); expect(onInitResult).toBe(true); }); diff --git a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 2411752baa7..02375d83b98 100644 --- a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -1,21 +1,22 @@ -import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; -import { Biome } from "#app/enums/biome"; -import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; -import { Species } from "#app/enums/species"; -import GameManager from "#app/test/utils/gameManager"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import BattleScene from "#app/battle-scene"; +import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter"; +import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; +import { Abilities } from "#enums/abilities"; +import { Biome } from "#enums/biome"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { Species } from "#enums/species"; +import { CommandPhase } from "#app/phases/command-phase"; +import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import GameManager from "#test/utils/gameManager"; +import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; -import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; -import { CommandPhase } from "#app/phases/command-phase"; -import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; -import { Mode } from "#app/ui/ui"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { Abilities } from "#app/enums/abilities"; +import i18next from "i18next"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const namespace = "mysteryEncounters/teleportingHijinks"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -300,8 +301,8 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; - expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === "modifierType:AttackTypeBoosterItem.metal_coat")).toBe(true); - expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === "modifierType:AttackTypeBoosterItem.magnet")).toBe(true); + expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.metal_coat"))).toBe(true); + expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.magnet"))).toBe(true); }); }); }); diff --git a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index 7e445ac1fe2..a3a43815ec6 100644 --- a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -124,10 +124,31 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { }); }); - it("should start battle against the trainer", async () => { + it("should start battle against the trainer with correctly loaded assets", async () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + + let successfullyLoaded = false; + vi.spyOn(scene, "getEnemyParty").mockImplementation(() => { + const ace = scene.currentBattle?.enemyParty[0]; + if (ace) { + // Pretend that loading assets takes an extra 500ms + vi.spyOn(ace, "loadAssets").mockImplementation(() => new Promise(resolve => { + setTimeout(() => { + successfullyLoaded = true; + resolve(); + }, 500); + })); + } + + return scene.currentBattle?.enemyParty ?? []; + }); + await runMysteryEncounterToEnd(game, 1, undefined, true); + // Check that assets are successfully loaded + expect(successfullyLoaded).toBe(true); + + // Check usual battle stuff expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); @@ -155,7 +176,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); - expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs); game.phaseInterceptor.superEndPhase(); await game.phaseInterceptor.to(PostMysteryEncounterPhase); @@ -182,10 +203,31 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { }); }); - it("should start battle against the trainer", async () => { + it("should start battle against the trainer with correctly loaded assets", async () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + + let successfullyLoaded = false; + vi.spyOn(scene, "getEnemyParty").mockImplementation(() => { + const ace = scene.currentBattle?.enemyParty[0]; + if (ace) { + // Pretend that loading assets takes an extra 500ms + vi.spyOn(ace, "loadAssets").mockImplementation(() => new Promise(resolve => { + setTimeout(() => { + successfullyLoaded = true; + resolve(); + }, 500); + })); + } + + return scene.currentBattle?.enemyParty ?? []; + }); + await runMysteryEncounterToEnd(game, 2, undefined, true); + // Check that assets are successfully loaded + expect(successfullyLoaded).toBe(true); + + // Check usual battle stuff expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); @@ -213,7 +255,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); - expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs); game.phaseInterceptor.superEndPhase(); await game.phaseInterceptor.to(PostMysteryEncounterPhase); @@ -240,10 +282,31 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { }); }); - it("should start battle against the trainer", async () => { + it("should start battle against the trainer with correctly loaded assets", async () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + + let successfullyLoaded = false; + vi.spyOn(scene, "getEnemyParty").mockImplementation(() => { + const ace = scene.currentBattle?.enemyParty[0]; + if (ace) { + // Pretend that loading assets takes an extra 500ms + vi.spyOn(ace, "loadAssets").mockImplementation(() => new Promise(resolve => { + setTimeout(() => { + successfullyLoaded = true; + resolve(); + }, 500); + })); + } + + return scene.currentBattle?.enemyParty ?? []; + }); + await runMysteryEncounterToEnd(game, 3, undefined, true); + // Check that assets are successfully loaded + expect(successfullyLoaded).toBe(true); + + // Check usual battle stuff expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); @@ -271,7 +334,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); - expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs); game.phaseInterceptor.superEndPhase(); await game.phaseInterceptor.to(PostMysteryEncounterPhase); diff --git a/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts b/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts index f8d1ffd3ded..040381c4ac3 100644 --- a/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts @@ -147,8 +147,8 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { expect(scene.getParty().length).toBe(initialPartySize + 1); - const newlyPurchasedPokemon = scene.getParty().find(p => p.name === pokemonName); - expect(newlyPurchasedPokemon).toBeDefined(); + const newlyPurchasedPokemon = scene.getParty()[scene.getParty().length - 1]; + expect(newlyPurchasedPokemon.name).toBe(pokemonName); expect(newlyPurchasedPokemon!.moveset.length > 0).toBeTruthy(); }); diff --git a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index f64124790b7..4d95ff31d36 100644 --- a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -21,7 +21,7 @@ import { BerryModifier, PokemonBaseStatTotalModifier } from "#app/modifier/modif import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; @@ -109,7 +109,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { species: getPokemonSpecies(Species.SHUCKLE), isBoss: true, bossSegments: 5, - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), + customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.BOLD, moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ], modifierConfigs: expect.any(Array), diff --git a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index f4d055c69aa..0d463655a52 100644 --- a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -122,7 +122,7 @@ describe("Weird Dream - Mystery Encounter", () => { for (let i = 0; i < pokemonAfter.length; i++) { const newPokemon = pokemonAfter[i]; expect(newPokemon.getSpeciesForm().speciesId).not.toBe(pokemonPrior[i].getSpeciesForm().speciesId); - expect(newPokemon.mysteryEncounterPokemonData?.types.length).toBe(2); + expect(newPokemon.customPokemonData?.types.length).toBe(2); } const plus90To110 = bstDiff.filter(bst => bst > 80); diff --git a/src/test/phases/mystery-encounter-phase.test.ts b/src/test/phases/mystery-encounter-phase.test.ts index 4468045756b..32e31ce1c94 100644 --- a/src/test/phases/mystery-encounter-phase.test.ts +++ b/src/test/phases/mystery-encounter-phase.test.ts @@ -9,6 +9,7 @@ import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import i18next from "i18next"; describe("Mystery Encounter Phases", () => { let phaserGame: Phaser.Game; @@ -78,9 +79,9 @@ describe("Mystery Encounter Phases", () => { expect(ui.getMode()).toBe(Mode.MESSAGE); expect(ui.showDialogue).toHaveBeenCalledTimes(1); expect(ui.showText).toHaveBeenCalledTimes(2); - expect(ui.showDialogue).toHaveBeenCalledWith("battle:mysteryEncounterAppeared", "???", null, expect.any(Function)); - expect(ui.showText).toHaveBeenCalledWith("mysteryEncounters/mysteriousChallengers:intro", null, expect.any(Function), 750, true); - expect(ui.showText).toHaveBeenCalledWith("mysteryEncounters/mysteriousChallengers:option.selected", null, expect.any(Function), 300, true); + expect(ui.showDialogue).toHaveBeenCalledWith(i18next.t("battle:mysteryEncounterAppeared"), "???", null, expect.any(Function)); + expect(ui.showText).toHaveBeenCalledWith(i18next.t("mysteryEncounters/mysteriousChallengers:intro"), null, expect.any(Function), 750, true); + expect(ui.showText).toHaveBeenCalledWith(i18next.t("mysteryEncounters/mysteriousChallengers:option.selected"), null, expect.any(Function), 300, true); }); }); diff --git a/src/test/phases/select-modifier-phase.test.ts b/src/test/phases/select-modifier-phase.test.ts index ea50c7e6524..a945aff055b 100644 --- a/src/test/phases/select-modifier-phase.test.ts +++ b/src/test/phases/select-modifier-phase.test.ts @@ -63,11 +63,11 @@ describe("SelectModifierPhase", () => { new ModifierTypeOption(modifierTypes.REVIVE(), 0, 1000) ]; - const selectModifierPhase1 = new SelectModifierPhase(scene); - const selectModifierPhase2 = new SelectModifierPhase(scene, 0, undefined, { rerollMultiplier: 2 }); + const selectModifierPhase1 = new SelectModifierPhase(scene, 0, undefined, { guaranteedModifierTypeOptions: options }); + const selectModifierPhase2 = new SelectModifierPhase(scene, 0, undefined, { guaranteedModifierTypeOptions: options, rerollMultiplier: 2 }); - const cost1 = selectModifierPhase1.getRerollCost(options, false); - const cost2 = selectModifierPhase2.getRerollCost(options, false); + const cost1 = selectModifierPhase1.getRerollCost(false); + const cost2 = selectModifierPhase2.getRerollCost(false); expect(cost2).toEqual(cost1 * 2); }); diff --git a/src/test/system/game_data.test.ts b/src/test/system/game_data.test.ts index 5eb4dea3910..fcb7e9067a3 100644 --- a/src/test/system/game_data.test.ts +++ b/src/test/system/game_data.test.ts @@ -11,13 +11,15 @@ import * as account from "../../account"; const apiBase = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8001"; -export const server = setupServer(); +/** We need a custom server. For some reasons I can't extend the listeners of {@linkcode global.i18nServer} with {@linkcode global.i18nServer.use} */ +const server = setupServer(); describe("System - Game Data", () => { let phaserGame: Phaser.Game; let game: GameManager; beforeAll(() => { + global.i18nServer.close(); server.listen(); phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -26,6 +28,7 @@ describe("System - Game Data", () => { afterAll(() => { server.close(); + global.i18nServer.listen(); }); beforeEach(() => { diff --git a/src/test/ui/starter-select.test.ts b/src/test/ui/starter-select.test.ts index dd0761be392..94370ca1b74 100644 --- a/src/test/ui/starter-select.test.ts +++ b/src/test/ui/starter-select.test.ts @@ -14,6 +14,7 @@ import { Abilities } from "#enums/abilities"; import { Button } from "#enums/buttons"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; +import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -66,11 +67,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -127,11 +128,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -191,11 +192,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -254,11 +255,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -315,11 +316,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -376,11 +377,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -436,11 +437,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -496,11 +497,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); let starterSelectUiHandler: StarterSelectUiHandler; @@ -561,11 +562,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); let starterSelectUiHandler: StarterSelectUiHandler | undefined; diff --git a/src/test/ui/type-hints.test.ts b/src/test/ui/type-hints.test.ts index 450f43f1263..2977262dda7 100644 --- a/src/test/ui/type-hints.test.ts +++ b/src/test/ui/type-hints.test.ts @@ -7,7 +7,8 @@ import { Mode } from "#app/ui/ui"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import MockText from "../utils/mocks/mocksContainer/mockText"; +import MockText from "#test/utils/mocks/mocksContainer/mockText"; +import i18next from "i18next"; describe("UI - Type Hints", () => { let phaserGame: Phaser.Game; @@ -53,7 +54,7 @@ describe("UI - Type Hints", () => { const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); const dragonClawText = movesContainer .getAll() - .find((text) => text.text === "move:dragonClaw.name")! as unknown as MockText; + .find((text) => text.text === i18next.t("move:dragonClaw.name"))! as unknown as MockText; expect.soft(dragonClawText.color).toBe("#929292"); ui.getHandler().processInput(Button.ACTION); @@ -78,7 +79,7 @@ describe("UI - Type Hints", () => { const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); const growlText = movesContainer .getAll() - .find((text) => text.text === "move:growl.name")! as unknown as MockText; + .find((text) => text.text === i18next.t("move:growl.name"))! as unknown as MockText; expect.soft(growlText.color).toBe(undefined); ui.getHandler().processInput(Button.ACTION); diff --git a/src/test/utils/gameManagerUtils.ts b/src/test/utils/gameManagerUtils.ts index 700d93082d8..543ee9627fe 100644 --- a/src/test/utils/gameManagerUtils.ts +++ b/src/test/utils/gameManagerUtils.ts @@ -86,7 +86,7 @@ export function waitUntil(truth) { export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: Moves) { const playerPokemon = scene.getPlayerField()[pokemonIndex]; const moveSet = playerPokemon.getMoveset(); - const index = moveSet.findIndex((m) => m?.moveId === move); + const index = moveSet.findIndex((m) => m?.moveId === move && m?.ppUsed < m?.getMovePp()); console.log(`Move position for ${Moves[move]} (=${move}):`, index); return index; } diff --git a/src/test/utils/gameWrapper.ts b/src/test/utils/gameWrapper.ts index 0ef5c4d4611..48c0007118b 100644 --- a/src/test/utils/gameWrapper.ts +++ b/src/test/utils/gameWrapper.ts @@ -23,6 +23,7 @@ import KeyboardPlugin = Phaser.Input.Keyboard.KeyboardPlugin; import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin; import EventEmitter = Phaser.Events.EventEmitter; import UpdateList = Phaser.GameObjects.UpdateList; +import { version } from "../../../package.json"; Object.defineProperty(window, "localStorage", { value: mockLocalStorage(), @@ -101,6 +102,7 @@ export default class GameWrapper { injectMandatory() { this.game.config = { seed: ["test"], + gameVersion: version }; this.scene.game = this.game; this.game.renderer = { diff --git a/src/test/utils/helpers/challengeModeHelper.ts b/src/test/utils/helpers/challengeModeHelper.ts index 184f11f505c..5210d942d5a 100644 --- a/src/test/utils/helpers/challengeModeHelper.ts +++ b/src/test/utils/helpers/challengeModeHelper.ts @@ -38,6 +38,10 @@ export class ChallengeModeHelper extends GameManagerHelper { async runToSummon(species?: Species[]) { await this.game.runToTitle(); + if (this.game.override.disableShinies) { + this.game.override.shiny(false).enemyShiny(false); + } + this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { this.game.scene.gameMode.challenges = this.challenges; const starters = generateStarter(this.game.scene, species); @@ -47,7 +51,7 @@ export class ChallengeModeHelper extends GameManagerHelper { }); await this.game.phaseInterceptor.run(EncounterPhase); - if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) { + if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) { this.game.removeEnemyHeldItems(); } } diff --git a/src/test/utils/helpers/classicModeHelper.ts b/src/test/utils/helpers/classicModeHelper.ts index 55e995fc9dc..80d0b86de7b 100644 --- a/src/test/utils/helpers/classicModeHelper.ts +++ b/src/test/utils/helpers/classicModeHelper.ts @@ -20,9 +20,13 @@ export class ClassicModeHelper extends GameManagerHelper { * @param species - Optional array of species to summon. * @returns A promise that resolves when the summon phase is reached. */ - async runToSummon(species?: Species[]) { + async runToSummon(species?: Species[]): Promise { await this.game.runToTitle(); + if (this.game.override.disableShinies) { + this.game.override.shiny(false).enemyShiny(false); + } + this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { this.game.scene.gameMode = getGameMode(GameModes.CLASSIC); const starters = generateStarter(this.game.scene, species); @@ -32,7 +36,7 @@ export class ClassicModeHelper extends GameManagerHelper { }); await this.game.phaseInterceptor.run(EncounterPhase); - if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) { + if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) { this.game.removeEnemyHeldItems(); } } @@ -42,7 +46,7 @@ export class ClassicModeHelper extends GameManagerHelper { * @param species - Optional array of species to start the battle with. * @returns A promise that resolves when the battle is started. */ - async startBattle(species?: Species[]) { + async startBattle(species?: Species[]): Promise { await this.runToSummon(species); if (this.game.scene.battleStyle === BattleStyle.SWITCH) { diff --git a/src/test/utils/helpers/dailyModeHelper.ts b/src/test/utils/helpers/dailyModeHelper.ts index e40fada8ac7..813544f85df 100644 --- a/src/test/utils/helpers/dailyModeHelper.ts +++ b/src/test/utils/helpers/dailyModeHelper.ts @@ -21,6 +21,10 @@ export class DailyModeHelper extends GameManagerHelper { async runToSummon() { await this.game.runToTitle(); + if (this.game.override.disableShinies) { + this.game.override.shiny(false).enemyShiny(false); + } + this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { const titlePhase = new TitlePhase(this.game.scene); titlePhase.initDailyRun(); @@ -33,7 +37,7 @@ export class DailyModeHelper extends GameManagerHelper { await this.game.phaseInterceptor.to(EncounterPhase); - if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) { + if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) { this.game.removeEnemyHeldItems(); } } diff --git a/src/test/utils/helpers/overridesHelper.ts b/src/test/utils/helpers/overridesHelper.ts index 27fd9552fe4..ec4d8dbbe4c 100644 --- a/src/test/utils/helpers/overridesHelper.ts +++ b/src/test/utils/helpers/overridesHelper.ts @@ -19,6 +19,11 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; * Helper to handle overrides in tests */ export class OverridesHelper extends GameManagerHelper { + /** If `true`, removes the starting items from enemies at the start of each test; default `true` */ + public removeEnemyStartingItems: boolean = true; + /** If `true`, sets the shiny overrides to disable shinies at the start of each test; default `true` */ + public disableShinies: boolean = true; + /** * Override the starting biome * @warning Any event listeners that are attached to [NewArenaEvent](events\battle-scene.ts) may need to be handled down the line @@ -368,23 +373,50 @@ export class OverridesHelper extends GameManagerHelper { /** * Override player shininess - * @param shininess Whether the player's Pokemon should be shiny. + * @param shininess - `true` or `false` to force the player's pokemon to be shiny or not shiny, + * `null` to disable the override and re-enable RNG shinies. */ - shinyLevel(shininess: boolean): this { + shiny(shininess: boolean | null): this { vi.spyOn(Overrides, "SHINY_OVERRIDE", "get").mockReturnValue(shininess); - this.log(`Set player Pokemon as ${shininess ? "" : "not "}shiny!`); + if (shininess === null) { + this.log("Disabled player Pokemon shiny override!"); + } else { + this.log(`Set player Pokemon to be ${shininess ? "" : "not "}shiny!`); + } return this; } + /** * Override player shiny variant - * @param variant The player's shiny variant. + * @param variant - The player's shiny variant. */ - variantLevel(variant: Variant): this { + shinyVariant(variant: Variant): this { vi.spyOn(Overrides, "VARIANT_OVERRIDE", "get").mockReturnValue(variant); this.log(`Set player Pokemon's shiny variant to ${variant}!`); return this; } + /** + * Override enemy shininess + * @param shininess - `true` or `false` to force the enemy's pokemon to be shiny or not shiny, + * `null` to disable the override and re-enable RNG shinies. + * @param variant - (Optional) The enemy's shiny {@linkcode Variant}. + */ + enemyShiny(shininess: boolean | null, variant?: Variant): this { + vi.spyOn(Overrides, "OPP_SHINY_OVERRIDE", "get").mockReturnValue(shininess); + if (shininess === null) { + this.log("Disabled enemy Pokemon shiny override!"); + } else { + this.log(`Set enemy Pokemon to be ${shininess ? "" : "not "}shiny!`); + } + + if (variant !== undefined) { + vi.spyOn(Overrides, "OPP_VARIANT_OVERRIDE", "get").mockReturnValue(variant); + this.log(`Set enemy shiny variant to be ${variant}!`); + } + return this; + } + /** * Override the enemy (Pokemon) to have the given amount of health segments * @param healthSegments the number of segments to give diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index d108c4cb2ea..ec9309e2405 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -53,6 +53,7 @@ import { } from "#app/phases/mystery-encounter-phases"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; +import { ExpPhase } from "#app/phases/exp-phase"; export interface PromptHandler { phaseTarget?: string; @@ -61,7 +62,114 @@ export interface PromptHandler { expireFn?: () => void; awaitingActionInput?: boolean; } -import { ExpPhase } from "#app/phases/exp-phase"; + +type PhaseClass = + | typeof LoginPhase + | typeof TitlePhase + | typeof SelectGenderPhase + | typeof EncounterPhase + | typeof NewBiomeEncounterPhase + | typeof SelectStarterPhase + | typeof PostSummonPhase + | typeof SummonPhase + | typeof ToggleDoublePositionPhase + | typeof CheckSwitchPhase + | typeof ShowAbilityPhase + | typeof MessagePhase + | typeof TurnInitPhase + | typeof CommandPhase + | typeof EnemyCommandPhase + | typeof TurnStartPhase + | typeof MovePhase + | typeof MoveEffectPhase + | typeof DamagePhase + | typeof FaintPhase + | typeof BerryPhase + | typeof TurnEndPhase + | typeof BattleEndPhase + | typeof EggLapsePhase + | typeof SelectModifierPhase + | typeof NextEncounterPhase + | typeof NewBattlePhase + | typeof VictoryPhase + | typeof LearnMovePhase + | typeof MoveEndPhase + | typeof StatStageChangePhase + | typeof ShinySparklePhase + | typeof SelectTargetPhase + | typeof UnavailablePhase + | typeof QuietFormChangePhase + | typeof SwitchPhase + | typeof SwitchSummonPhase + | typeof PartyHealPhase + | typeof EvolutionPhase + | typeof EndEvolutionPhase + | typeof LevelCapPhase + | typeof AttemptRunPhase + | typeof SelectBiomePhase + | typeof MysteryEncounterPhase + | typeof MysteryEncounterOptionSelectedPhase + | typeof MysteryEncounterBattlePhase + | typeof MysteryEncounterRewardsPhase + | typeof PostMysteryEncounterPhase + | typeof ModifierRewardPhase + | typeof PartyExpPhase + | typeof ExpPhase; + +type PhaseString = + | "LoginPhase" + | "TitlePhase" + | "SelectGenderPhase" + | "EncounterPhase" + | "NewBiomeEncounterPhase" + | "SelectStarterPhase" + | "PostSummonPhase" + | "SummonPhase" + | "ToggleDoublePositionPhase" + | "CheckSwitchPhase" + | "ShowAbilityPhase" + | "MessagePhase" + | "TurnInitPhase" + | "CommandPhase" + | "EnemyCommandPhase" + | "TurnStartPhase" + | "MovePhase" + | "MoveEffectPhase" + | "DamagePhase" + | "FaintPhase" + | "BerryPhase" + | "TurnEndPhase" + | "BattleEndPhase" + | "EggLapsePhase" + | "SelectModifierPhase" + | "NextEncounterPhase" + | "NewBattlePhase" + | "VictoryPhase" + | "LearnMovePhase" + | "MoveEndPhase" + | "StatStageChangePhase" + | "ShinySparklePhase" + | "SelectTargetPhase" + | "UnavailablePhase" + | "QuietFormChangePhase" + | "SwitchPhase" + | "SwitchSummonPhase" + | "PartyHealPhase" + | "EvolutionPhase" + | "EndEvolutionPhase" + | "LevelCapPhase" + | "AttemptRunPhase" + | "SelectBiomePhase" + | "MysteryEncounterPhase" + | "MysteryEncounterOptionSelectedPhase" + | "MysteryEncounterBattlePhase" + | "MysteryEncounterRewardsPhase" + | "PostMysteryEncounterPhase" + | "ModifierRewardPhase" + | "PartyExpPhase" + | "ExpPhase"; + +type PhaseInterceptorPhase = PhaseClass | PhaseString; export default class PhaseInterceptor { public scene; @@ -172,7 +280,7 @@ export default class PhaseInterceptor { * @param phaseFrom - The phase to start from. * @returns The instance of the PhaseInterceptor. */ - runFrom(phaseFrom) { + runFrom(phaseFrom: PhaseInterceptorPhase): PhaseInterceptor { this.phaseFrom = phaseFrom; return this; } @@ -180,9 +288,10 @@ export default class PhaseInterceptor { /** * Method to transition to a target phase. * @param phaseTo - The phase to transition to. + * @param runTarget - Whether or not to run the target phase. * @returns A promise that resolves when the transition is complete. */ - async to(phaseTo, runTarget: boolean = true): Promise { + async to(phaseTo: PhaseInterceptorPhase, runTarget: boolean = true): Promise { return new Promise(async (resolve, reject) => { ErrorInterceptor.getInstance().add(this); if (this.phaseFrom) { @@ -219,7 +328,7 @@ export default class PhaseInterceptor { * @param skipFn - Optional skip function. * @returns A promise that resolves when the phase is run. */ - run(phaseTarget, skipFn?): Promise { + run(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise { const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; this.scene.moveAnimations = null; // Mandatory to avoid crash return new Promise(async (resolve, reject) => { @@ -253,7 +362,7 @@ export default class PhaseInterceptor { }); } - whenAboutToRun(phaseTarget, skipFn?): Promise { + whenAboutToRun(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise { const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; this.scene.moveAnimations = null; // Mandatory to avoid crash return new Promise(async (resolve, reject) => { @@ -311,7 +420,7 @@ export default class PhaseInterceptor { * Method to start a phase and log it. * @param phase - The phase to start. */ - startPhase(phase) { + startPhase(phase: PhaseClass) { this.log.push(phase.name); const instance = this.scene.getCurrentPhase(); this.onHold.push({ @@ -340,9 +449,10 @@ export default class PhaseInterceptor { /** * m2m to set mode. - * @param phase - The phase to start. + * @param mode - The {@linkcode Mode} to set. + * @param args - Additional arguments to pass to the original method. */ - setMode(mode: Mode, ...args: any[]): Promise { + setMode(mode: Mode, ...args: unknown[]): Promise { const currentPhase = this.scene.getCurrentPhase(); const instance = this.scene.ui; console.log("setMode", `${Mode[mode]} (=${mode})`, args); diff --git a/src/test/vitest.setup.ts b/src/test/vitest.setup.ts index 0d67d6787c4..8438f607db2 100644 --- a/src/test/vitest.setup.ts +++ b/src/test/vitest.setup.ts @@ -4,16 +4,17 @@ import { initLoggedInUser } from "#app/account"; import { initAbilities } from "#app/data/ability"; import { initBiomes } from "#app/data/balance/biomes"; import { initEggMoves } from "#app/data/balance/egg-moves"; +import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { initMoves } from "#app/data/move"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; -import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { initPokemonForms } from "#app/data/pokemon-forms"; import { initSpecies } from "#app/data/pokemon-species"; import { initAchievements } from "#app/system/achv"; import { initVouchers } from "#app/system/voucher"; import { initStatsKeys } from "#app/ui/game-stats-ui-handler"; -import { beforeAll, vi } from "vitest"; +import { afterAll, beforeAll, vi } from "vitest"; +/** Set the timezone to UTC for tests. */ process.env.TZ = "UTC"; /** Mock the override import to always return default values, ignoring any custom overrides. */ @@ -26,26 +27,36 @@ vi.mock("#app/overrides", async (importOriginal) => { } satisfies typeof import("#app/overrides"); }); -vi.mock("i18next", () => ({ - default: { - use: () => {}, - t: (key: string) => key, - changeLanguage: () => Promise.resolve(), - init: () => Promise.resolve(), - resolvedLanguage: "en", - exists: () => true, - getDataByLanguage:() => ({ - en: { - keys: [ "foo" ] - }, - }), - services: { - formatter: { - add: () => {}, +/** + * This is a hacky way to mock the i18n backend requests (with the help of {@link https://mswjs.io/ | msw}). + * The reason to put it inside of a mock is to elevate it. + * This is necessary because how our code is structured. + * Do NOT try to put any of this code into external functions, it won't work as it's elevated during runtime. + */ +vi.mock("i18next", async (importOriginal) => { + console.log("Mocking i18next"); + const { setupServer } = await import("msw/node"); + const { http, HttpResponse } = await import("msw"); + + global.i18nServer = setupServer( + http.get("/locales/en/*", async (req) => { + const filename = req.params[0]; + + try { + const json = await import(`../../public/locales/en/${req.params[0]}`); + console.log("Loaded locale", filename); + return HttpResponse.json(json); + } catch (err) { + console.log(`Failed to load locale ${filename}!`, err); + return HttpResponse.json({}); } - }, - }, -})); + }) + ); + global.i18nServer.listen({ onUnhandledRequest: "error" }); + console.log("i18n MSW server listening!"); + + return await importOriginal(); +}); initVouchers(); initAchievements(); @@ -70,3 +81,8 @@ beforeAll(() => { }, }); }); + +afterAll(() => { + global.i18nServer.close(); + console.log("Closing i18n MSW server!"); +}); diff --git a/src/touch-controls.ts b/src/touch-controls.ts index 786d1203d12..93032ce59fe 100644 --- a/src/touch-controls.ts +++ b/src/touch-controls.ts @@ -122,20 +122,20 @@ export default class TouchControl { const button = Button[key]; switch (eventType) { - case "keydown": - this.events.emit("input_down", { - controller_type: "keyboard", - button: button, - isTouch: true - }); - break; - case "keyup": - this.events.emit("input_up", { - controller_type: "keyboard", - button: button, - isTouch: true - }); - break; + case "keydown": + this.events.emit("input_down", { + controller_type: "keyboard", + button: button, + isTouch: true + }); + break; + case "keyup": + this.events.emit("input_up", { + controller_type: "keyboard", + button: button, + isTouch: true + }); + break; } return true; } diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 9ea1276352a..92b1653df3d 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -168,26 +168,26 @@ export class UiInputs { return; } switch (this.scene.ui?.getMode()) { - case Mode.MESSAGE: - const messageHandler = this.scene.ui.getHandler(); - if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) { + case Mode.MESSAGE: + const messageHandler = this.scene.ui.getHandler(); + if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) { + return; + } + case Mode.TITLE: + case Mode.COMMAND: + case Mode.MODIFIER_SELECT: + case Mode.MYSTERY_ENCOUNTER: + this.scene.ui.setOverlayMode(Mode.MENU); + break; + case Mode.STARTER_SELECT: + this.buttonTouch(); + break; + case Mode.MENU: + this.scene.ui.revertMode(); + this.scene.playSound("ui/select"); + break; + default: return; - } - case Mode.TITLE: - case Mode.COMMAND: - case Mode.MODIFIER_SELECT: - case Mode.MYSTERY_ENCOUNTER: - this.scene.ui.setOverlayMode(Mode.MENU); - break; - case Mode.STARTER_SELECT: - this.buttonTouch(); - break; - case Mode.MENU: - this.scene.ui.revertMode(); - this.scene.playSound("ui/select"); - break; - default: - return; } } diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index 9dffd3b4ad0..01fc5b00014 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -165,6 +165,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { if (this.config.delay) { this.blockInput = true; this.optionSelectText.setAlpha(0.5); + this.cursorObj?.setAlpha(0.8); this.scene.time.delayedCall(Utils.fixedInt(this.config.delay), () => this.unblockInput()); } @@ -221,20 +222,20 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { } } else { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else if (this.cursor === 0) { - success = this.setCursor(options.length - 1); - } - break; - case Button.DOWN: - if (this.cursor < options.length - 1) { - success = this.setCursor(this.cursor + 1); - } else { - success = this.setCursor(0); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else if (this.cursor === 0) { + success = this.setCursor(options.length - 1); + } + break; + case Button.DOWN: + if (this.cursor < options.length - 1) { + success = this.setCursor(this.cursor + 1); + } else { + success = this.setCursor(0); + } + break; } if (this.config?.supportHover) { // handle hover code if the element supports hover-handlers and the option has the optional hover-handler set. @@ -256,6 +257,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { this.blockInput = false; this.optionSelectText.setAlpha(1); + this.cursorObj?.setAlpha(1); } getOptionsWithScroll(): OptionSelectItem[] { diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index f90732d1fae..a1ced6145eb 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -40,7 +40,9 @@ export default class AchvsUiHandler extends MessageUiHandler { private iconsBg: Phaser.GameObjects.NineSlice; private icons: Phaser.GameObjects.Sprite[]; + private titleBg: Phaser.GameObjects.NineSlice; private titleText: Phaser.GameObjects.Text; + private scoreContainer: Phaser.GameObjects.Container; private scoreText: Phaser.GameObjects.Text; private unlockText: Phaser.GameObjects.Text; @@ -114,29 +116,31 @@ export default class AchvsUiHandler extends MessageUiHandler { const titleBg = addWindow(this.scene, 0, this.headerBg.height + this.iconsBg.height, 174, 24); titleBg.setOrigin(0, 0); + this.titleBg = titleBg; this.titleText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW); const textSize = languageSettings[i18next.language]?.TextSize ?? this.titleText.style.fontSize; this.titleText.setFontSize(textSize); - this.titleText.setOrigin(0, 0); const titleBgCenterX = titleBg.x + titleBg.width / 2; const titleBgCenterY = titleBg.y + titleBg.height / 2; this.titleText.setOrigin(0.5, 0.5); this.titleText.setPosition(titleBgCenterX, titleBgCenterY); - const scoreBg = addWindow(this.scene, titleBg.x + titleBg.width, titleBg.y, 46, 24); + this.scoreContainer = this.scene.add.container(titleBg.x + titleBg.width, titleBg.y); + const scoreBg = addWindow(this.scene, 0, 0, 46, 24); scoreBg.setOrigin(0, 0); + this.scoreContainer.add(scoreBg); - this.scoreText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW); - this.scoreText.setOrigin(0, 0); - this.scoreText.setPositionRelative(scoreBg, 8, 4); + this.scoreText = addTextObject(this.scene, scoreBg.width / 2, scoreBg.height / 2, "", TextStyle.WINDOW); + this.scoreText.setOrigin(0.5, 0.5); + this.scoreContainer.add(this.scoreText); - const unlockBg = addWindow(this.scene, scoreBg.x + scoreBg.width, scoreBg.y, 98, 24); + const unlockBg = addWindow(this.scene, this.scoreContainer.x + scoreBg.width, titleBg.y, 98, 24); unlockBg.setOrigin(0, 0); this.unlockText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW); - this.unlockText.setOrigin(0, 0); - this.unlockText.setPositionRelative(unlockBg, 8, 4); + this.unlockText.setOrigin(0.5, 0.5); + this.unlockText.setPositionRelative(unlockBg, unlockBg.width / 2, unlockBg.height / 2); const descriptionBg = addWindow(this.scene, 0, titleBg.y + titleBg.height, (this.scene.game.canvas.width / 6) - 2, 42); descriptionBg.setOrigin(0, 0); @@ -157,8 +161,7 @@ export default class AchvsUiHandler extends MessageUiHandler { this.mainContainer.add(this.iconsContainer); this.mainContainer.add(titleBg); this.mainContainer.add(this.titleText); - this.mainContainer.add(scoreBg); - this.mainContainer.add(this.scoreText); + this.mainContainer.add(this.scoreContainer); this.mainContainer.add(unlockBg); this.mainContainer.add(this.unlockText); this.mainContainer.add(descriptionBg); @@ -167,8 +170,6 @@ export default class AchvsUiHandler extends MessageUiHandler { ui.add(this.mainContainer); this.currentPage = Page.ACHIEVEMENTS; - this.setCursor(0); - this.setScrollCursor(0); this.mainContainer.setVisible(false); } @@ -244,51 +245,51 @@ export default class AchvsUiHandler extends MessageUiHandler { const rowIndex = Math.floor(this.cursor / this.COLS); const itemOffset = (this.scrollCursor * this.COLS); switch (button) { - case Button.UP: - if (this.cursor < this.COLS) { - if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1); - } else { + case Button.UP: + if (this.cursor < this.COLS) { + if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1); + } else { // Wrap around to the last row - success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS); - let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS; - if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS - 1) { - newCursorIndex -= this.COLS; + success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS); + let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS; + if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS - 1) { + newCursorIndex -= this.COLS; + } + success = success && this.setCursor(newCursorIndex); } - success = success && this.setCursor(newCursorIndex); - } - } else { - success = this.setCursor(this.cursor - this.COLS); - } - break; - case Button.DOWN: - const canMoveDown = itemOffset + 1 < this.currentTotal; - if (rowIndex >= this.ROWS - 1) { - if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) { - // scroll down one row - success = this.setScrollCursor(this.scrollCursor + 1); } else { - // wrap back to the first row - success = this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS); + success = this.setCursor(this.cursor - this.COLS); } - } else if (canMoveDown) { - success = this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1)); - } - break; - case Button.LEFT: - if (this.cursor % this.COLS === 0) { - success = this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1)); - } else { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.RIGHT: - if ((this.cursor + 1) % this.COLS === 0 || (this.cursor + itemOffset) === (this.currentTotal - 1)) { - success = this.setCursor(this.cursor - this.cursor % this.COLS); - } else { - success = this.setCursor(this.cursor + 1); - } - break; + break; + case Button.DOWN: + const canMoveDown = itemOffset + 1 < this.currentTotal; + if (rowIndex >= this.ROWS - 1) { + if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) { + // scroll down one row + success = this.setScrollCursor(this.scrollCursor + 1); + } else { + // wrap back to the first row + success = this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS); + } + } else if (canMoveDown) { + success = this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1)); + } + break; + case Button.LEFT: + if (this.cursor % this.COLS === 0) { + success = this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1)); + } else { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.RIGHT: + if ((this.cursor + 1) % this.COLS === 0 || (this.cursor + itemOffset) === (this.currentTotal - 1)) { + success = this.setCursor(this.cursor - this.cursor % this.COLS); + } else { + success = this.setCursor(this.cursor + 1); + } + break; } } @@ -315,12 +316,22 @@ export default class AchvsUiHandler extends MessageUiHandler { if (update || pageChange) { switch (this.currentPage) { - case Page.ACHIEVEMENTS: - this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]); - break; - case Page.VOUCHERS: - this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]); - break; + case Page.ACHIEVEMENTS: + if (pageChange) { + this.titleBg.width = 174; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(true); + } + this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]); + break; + case Page.VOUCHERS: + if (pageChange) { + this.titleBg.width = 220; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(false); + } + this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]); + break; } } return ret; @@ -347,14 +358,14 @@ export default class AchvsUiHandler extends MessageUiHandler { } switch (this.currentPage) { - case Page.ACHIEVEMENTS: - this.updateAchvIcons(); - this.showAchv(achvs[Object.keys(achvs)[this.cursor + this.scrollCursor * this.COLS]]); - break; - case Page.VOUCHERS: - this.updateVoucherIcons(); - this.showVoucher(vouchers[Object.keys(vouchers)[this.cursor + this.scrollCursor * this.COLS]]); - break; + case Page.ACHIEVEMENTS: + this.updateAchvIcons(); + this.showAchv(achvs[Object.keys(achvs)[this.cursor + this.scrollCursor * this.COLS]]); + break; + case Page.VOUCHERS: + this.updateVoucherIcons(); + this.showVoucher(vouchers[Object.keys(vouchers)[this.cursor + this.scrollCursor * this.COLS]]); + break; } return true; } @@ -442,6 +453,7 @@ export default class AchvsUiHandler extends MessageUiHandler { this.currentPage = Page.ACHIEVEMENTS; this.mainContainer.setVisible(false); this.setScrollCursor(0); + this.setCursor(0, true); this.eraseCursor(); } diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 0c00d3403b6..a82f97244cd 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -215,20 +215,20 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { // Creates a proxy object to decide which text object needs to be updated let textObject: Phaser.GameObjects.Text; switch (fieldEffectInfo.effecType) { - case ArenaEffectType.PLAYER: - textObject = this.flyoutTextPlayer; - break; + case ArenaEffectType.PLAYER: + textObject = this.flyoutTextPlayer; + break; - case ArenaEffectType.WEATHER: - case ArenaEffectType.TERRAIN: - case ArenaEffectType.FIELD: - textObject = this.flyoutTextField; + case ArenaEffectType.WEATHER: + case ArenaEffectType.TERRAIN: + case ArenaEffectType.FIELD: + textObject = this.flyoutTextField; - break; + break; - case ArenaEffectType.ENEMY: - textObject = this.flyoutTextEnemy; - break; + case ArenaEffectType.ENEMY: + textObject = this.flyoutTextEnemy; + break; } textObject.text += fieldEffectInfo.name; @@ -253,81 +253,81 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { let foundIndex: number; switch (arenaEffectChangedEvent.constructor) { - case TagAddedEvent: - const tagAddedEvent = arenaEffectChangedEvent as TagAddedEvent; - const isArenaTrapTag = this.battleScene.arena.getTag(tagAddedEvent.arenaTagType) instanceof ArenaTrapTag; - let arenaEffectType: ArenaEffectType; + case TagAddedEvent: + const tagAddedEvent = arenaEffectChangedEvent as TagAddedEvent; + const isArenaTrapTag = this.battleScene.arena.getTag(tagAddedEvent.arenaTagType) instanceof ArenaTrapTag; + let arenaEffectType: ArenaEffectType; - if (tagAddedEvent.arenaTagSide === ArenaTagSide.BOTH) { - arenaEffectType = ArenaEffectType.FIELD; - } else if (tagAddedEvent.arenaTagSide === ArenaTagSide.PLAYER) { - arenaEffectType = ArenaEffectType.PLAYER; - } else { - arenaEffectType = ArenaEffectType.ENEMY; - } - - const existingTrapTagIndex = isArenaTrapTag ? this.fieldEffectInfo.findIndex(e => tagAddedEvent.arenaTagType === e.tagType && arenaEffectType === e.effecType) : -1; - let name: string = getFieldEffectText(ArenaTagType[tagAddedEvent.arenaTagType]); - - if (isArenaTrapTag) { - if (existingTrapTagIndex !== -1) { - const layers = tagAddedEvent.arenaTagMaxLayers > 1 ? ` (${tagAddedEvent.arenaTagLayers})` : ""; - this.fieldEffectInfo[existingTrapTagIndex].name = `${name}${layers}`; - break; - } else if (tagAddedEvent.arenaTagMaxLayers > 1) { - name = `${name} (${tagAddedEvent.arenaTagLayers})`; + if (tagAddedEvent.arenaTagSide === ArenaTagSide.BOTH) { + arenaEffectType = ArenaEffectType.FIELD; + } else if (tagAddedEvent.arenaTagSide === ArenaTagSide.PLAYER) { + arenaEffectType = ArenaEffectType.PLAYER; + } else { + arenaEffectType = ArenaEffectType.ENEMY; } - } - this.fieldEffectInfo.push({ - name, - effecType: arenaEffectType, - maxDuration: tagAddedEvent.duration, - duration: tagAddedEvent.duration, - tagType: tagAddedEvent.arenaTagType - }); - break; - case TagRemovedEvent: - const tagRemovedEvent = arenaEffectChangedEvent as TagRemovedEvent; - foundIndex = this.fieldEffectInfo.findIndex(info => info.tagType === tagRemovedEvent.arenaTagType); + const existingTrapTagIndex = isArenaTrapTag ? this.fieldEffectInfo.findIndex(e => tagAddedEvent.arenaTagType === e.tagType && arenaEffectType === e.effecType) : -1; + let name: string = getFieldEffectText(ArenaTagType[tagAddedEvent.arenaTagType]); - if (foundIndex !== -1) { // If the tag was being tracked, remove it - this.fieldEffectInfo.splice(foundIndex, 1); - } - break; + if (isArenaTrapTag) { + if (existingTrapTagIndex !== -1) { + const layers = tagAddedEvent.arenaTagMaxLayers > 1 ? ` (${tagAddedEvent.arenaTagLayers})` : ""; + this.fieldEffectInfo[existingTrapTagIndex].name = `${name}${layers}`; + break; + } else if (tagAddedEvent.arenaTagMaxLayers > 1) { + name = `${name} (${tagAddedEvent.arenaTagLayers})`; + } + } - case WeatherChangedEvent: - case TerrainChangedEvent: - const fieldEffectChangedEvent = arenaEffectChangedEvent as WeatherChangedEvent | TerrainChangedEvent; + this.fieldEffectInfo.push({ + name, + effecType: arenaEffectType, + maxDuration: tagAddedEvent.duration, + duration: tagAddedEvent.duration, + tagType: tagAddedEvent.arenaTagType + }); + break; + case TagRemovedEvent: + const tagRemovedEvent = arenaEffectChangedEvent as TagRemovedEvent; + foundIndex = this.fieldEffectInfo.findIndex(info => info.tagType === tagRemovedEvent.arenaTagType); - // Stores the old Weather/Terrain name in case it's in the array already - const oldName = + if (foundIndex !== -1) { // If the tag was being tracked, remove it + this.fieldEffectInfo.splice(foundIndex, 1); + } + break; + + case WeatherChangedEvent: + case TerrainChangedEvent: + const fieldEffectChangedEvent = arenaEffectChangedEvent as WeatherChangedEvent | TerrainChangedEvent; + + // Stores the old Weather/Terrain name in case it's in the array already + const oldName = getFieldEffectText(fieldEffectChangedEvent instanceof WeatherChangedEvent ? WeatherType[fieldEffectChangedEvent.oldWeatherType] : TerrainType[fieldEffectChangedEvent.oldTerrainType]); - // Stores the new Weather/Terrain info - const newInfo = { - name: + // Stores the new Weather/Terrain info + const newInfo = { + name: getFieldEffectText(fieldEffectChangedEvent instanceof WeatherChangedEvent ? WeatherType[fieldEffectChangedEvent.newWeatherType] : TerrainType[fieldEffectChangedEvent.newTerrainType]), - effecType: fieldEffectChangedEvent instanceof WeatherChangedEvent - ? ArenaEffectType.WEATHER - : ArenaEffectType.TERRAIN, - maxDuration: fieldEffectChangedEvent.duration, - duration: fieldEffectChangedEvent.duration }; + effecType: fieldEffectChangedEvent instanceof WeatherChangedEvent + ? ArenaEffectType.WEATHER + : ArenaEffectType.TERRAIN, + maxDuration: fieldEffectChangedEvent.duration, + duration: fieldEffectChangedEvent.duration }; - foundIndex = this.fieldEffectInfo.findIndex(info => [ newInfo.name, oldName ].includes(info.name)); - if (foundIndex === -1) { - if (newInfo.name !== undefined) { - this.fieldEffectInfo.push(newInfo); // Adds the info to the array if it doesn't already exist and is defined + foundIndex = this.fieldEffectInfo.findIndex(info => [ newInfo.name, oldName ].includes(info.name)); + if (foundIndex === -1) { + if (newInfo.name !== undefined) { + this.fieldEffectInfo.push(newInfo); // Adds the info to the array if it doesn't already exist and is defined + } + } else if (!newInfo.name) { + this.fieldEffectInfo.splice(foundIndex, 1); // Removes the old info if the new one is undefined + } else { + this.fieldEffectInfo[foundIndex] = newInfo; // Otherwise, replace the old info } - } else if (!newInfo.name) { - this.fieldEffectInfo.splice(foundIndex, 1); // Removes the old info if the new one is undefined - } else { - this.fieldEffectInfo[foundIndex] = newInfo; // Otherwise, replace the old info - } - break; + break; } this.updateFieldText(); diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index 0d97a79943d..9df6da36055 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -90,12 +90,12 @@ export default class BallUiHandler extends UiHandler { } } else { switch (button) { - case Button.UP: - success = this.setCursor(this.cursor ? this.cursor - 1 : pokeballTypeCount); - break; - case Button.DOWN: - success = this.setCursor(this.cursor < pokeballTypeCount ? this.cursor + 1 : 0); - break; + case Button.UP: + success = this.setCursor(this.cursor ? this.cursor - 1 : pokeballTypeCount); + break; + case Button.DOWN: + success = this.setCursor(this.cursor < pokeballTypeCount ? this.cursor + 1 : 0); + break; } } diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 79b51ba6c44..1d97998f491 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -593,7 +593,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { }; const updatePokemonHp = () => { - let duration = !instant ? Utils.clampInt(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0; + let duration = !instant ? Phaser.Math.Clamp(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0; const speed = (this.scene as BattleScene).hpBarSpeed; if (speed) { duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index b2c0f1a1746..e2547a626de 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -376,66 +376,66 @@ export default class GameChallengesUiHandler extends UiHandler { } else { if (this.cursorObj?.visible && !this.startCursor.visible) { switch (button) { - case Button.UP: - if (this.cursor === 0) { - if (this.scrollCursor === 0) { + case Button.UP: + if (this.cursor === 0) { + if (this.scrollCursor === 0) { // When at the top of the menu and pressing UP, move to the bottommost item. - if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom + if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(rowsToDisplay - 1); - // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay); - success = successA && successB; // success is just there to play the little validation sound effect - } else { // If there are 9 or less challenges, just move to the bottom one - success = this.setCursor(this.scene.gameMode.challenges.length - 1); + const successA = this.setCursor(rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect + } else { // If there are 9 or less challenges, just move to the bottom one + success = this.setCursor(this.scene.gameMode.challenges.length - 1); + } + } else { + success = this.setScrollCursor(this.scrollCursor - 1); } } else { - success = this.setScrollCursor(this.scrollCursor - 1); + success = this.setCursor(this.cursor - 1); } - } else { - success = this.setCursor(this.cursor - 1); - } - if (success) { - this.updateText(); - } - break; - case Button.DOWN: - if (this.cursor === rowsToDisplay - 1) { - if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) { + if (success) { + this.updateText(); + } + break; + case Button.DOWN: + if (this.cursor === rowsToDisplay - 1) { + if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) { // When at the bottom and pressing DOWN, scroll if possible. - success = this.setScrollCursor(this.scrollCursor + 1); - } else { + success = this.setScrollCursor(this.scrollCursor + 1); + } else { // When at the bottom of a scrolling menu and pressing DOWN, move to the topmost item. // First, set the cursor to the first visible element, preparing for the scroll to the top. - const successA = this.setCursor(0); - // Then, adjust the scroll to display the topmost elements of the menu. - const successB = this.setScrollCursor(0); - success = successA && successB; // success is just there to play the little validation sound effect - } - } else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) { + const successA = this.setCursor(0); + // Then, adjust the scroll to display the topmost elements of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // success is just there to play the little validation sound effect + } + } else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) { // When at the bottom of a non-scrolling menu and pressing DOWN, move to the topmost item. - success = this.setCursor(0); - } else { - success = this.setCursor(this.cursor + 1); - } - if (success) { - this.updateText(); - } - break; - case Button.LEFT: + success = this.setCursor(0); + } else { + success = this.setCursor(this.cursor + 1); + } + if (success) { + this.updateText(); + } + break; + case Button.LEFT: // Moves the option cursor left, if possible. - success = this.getActiveChallenge().decreaseValue(); - if (success) { - this.updateText(); - } - break; - case Button.RIGHT: + success = this.getActiveChallenge().decreaseValue(); + if (success) { + this.updateText(); + } + break; + case Button.RIGHT: // Moves the option cursor right, if possible. - success = this.getActiveChallenge().increaseValue(); - if (success) { - this.updateText(); - } - break; + success = this.getActiveChallenge().increaseValue(); + if (success) { + this.updateText(); + } + break; } } } diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index a2cce92ba51..95f12d68e92 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -102,54 +102,54 @@ export default class CommandUiHandler extends UiHandler { if (button === Button.ACTION) { switch (cursor) { // Fight - case Command.FIGHT: - if ((this.scene.getCurrentPhase() as CommandPhase).checkFightOverride()) { - return true; - } - ui.setMode(Mode.FIGHT, (this.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); - success = true; - break; + case Command.FIGHT: + if ((this.scene.getCurrentPhase() as CommandPhase).checkFightOverride()) { + return true; + } + ui.setMode(Mode.FIGHT, (this.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + success = true; + break; // Ball - case Command.BALL: - ui.setModeWithoutClear(Mode.BALL); - success = true; - break; + case Command.BALL: + ui.setModeWithoutClear(Mode.BALL); + success = true; + break; // Pokemon - case Command.POKEMON: - ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), null, PartyUiHandler.FilterNonFainted); - success = true; - break; + case Command.POKEMON: + ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), null, PartyUiHandler.FilterNonFainted); + success = true; + break; // Run - case Command.RUN: - (this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); - success = true; - break; + case Command.RUN: + (this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); + success = true; + break; } } else { (this.scene.getCurrentPhase() as CommandPhase).cancel(); } } else { switch (button) { - case Button.UP: - if (cursor >= 2) { - success = this.setCursor(cursor - 2); - } - break; - case Button.DOWN: - if (cursor < 2) { - success = this.setCursor(cursor + 2); - } - break; - case Button.LEFT: - if (cursor % 2 === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor % 2 === 0) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor >= 2) { + success = this.setCursor(cursor - 2); + } + break; + case Button.DOWN: + if (cursor < 2) { + success = this.setCursor(cursor + 2); + } + break; + case Button.LEFT: + if (cursor % 2 === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor % 2 === 0) { + success = this.setCursor(cursor + 1); + } + break; } } diff --git a/src/ui/confirm-ui-handler.ts b/src/ui/confirm-ui-handler.ts index 16dae82d091..2022508fc0d 100644 --- a/src/ui/confirm-ui-handler.ts +++ b/src/ui/confirm-ui-handler.ts @@ -76,7 +76,8 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { } } ], - delay: args.length >= 6 && args[5] !== null ? args[5] as integer : 0 + delay: args.length >= 6 && args[5] !== null ? args[5] as number : 0, + noCancel: args.length >= 7 && args[6] !== null ? args[6] as boolean : false, }; super.show([ config ]); @@ -96,7 +97,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { } processInput(button: Button): boolean { - if (button === Button.CANCEL && this.blockInput) { + if (button === Button.CANCEL && this.blockInput && !this.config?.noCancel) { this.unblockInput(); } diff --git a/src/ui/daily-run-scoreboard.ts b/src/ui/daily-run-scoreboard.ts index b535a94d35c..b9c1c6ea49a 100644 --- a/src/ui/daily-run-scoreboard.ts +++ b/src/ui/daily-run-scoreboard.ts @@ -142,13 +142,13 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { entryContainer.add(scoreLabel); switch (this.category) { - case ScoreboardCategory.DAILY: - const waveLabel = addTextObject(this.scene, 68, 0, wave, TextStyle.WINDOW, { fontSize: "54px" }); - entryContainer.add(waveLabel); - break; - case ScoreboardCategory.WEEKLY: - scoreLabel.x -= 16; - break; + case ScoreboardCategory.DAILY: + const waveLabel = addTextObject(this.scene, 68, 0, wave, TextStyle.WINDOW, { fontSize: "54px" }); + entryContainer.add(waveLabel); + break; + case ScoreboardCategory.WEEKLY: + scoreLabel.x -= 16; + break; } return entryContainer; diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts index eec44480c89..e16efe17036 100644 --- a/src/ui/dropdown.ts +++ b/src/ui/dropdown.ts @@ -115,18 +115,18 @@ export class DropDownOption extends Phaser.GameObjects.Container { */ private updateToggleIconColor(): void { switch (this.state) { - case DropDownState.ON: - this.toggle.setTint(this.onColor); - break; - case DropDownState.OFF: - this.toggle.setTint(this.offColor); - break; - case DropDownState.EXCLUDE: - this.toggle.setTint(this.excludeColor); - break; - case DropDownState.UNLOCKABLE: - this.toggle.setTint(this.unlockableColor); - break; + case DropDownState.ON: + this.toggle.setTint(this.onColor); + break; + case DropDownState.OFF: + this.toggle.setTint(this.offColor); + break; + case DropDownState.EXCLUDE: + this.toggle.setTint(this.excludeColor); + break; + case DropDownState.UNLOCKABLE: + this.toggle.setTint(this.unlockableColor); + break; } } @@ -500,18 +500,18 @@ export class DropDown extends Phaser.GameObjects.Container { }; switch (this.dropDownType) { - case DropDownType.MULTI: - case DropDownType.RADIAL: - return compareValues([ "val", "state" ]); + case DropDownType.MULTI: + case DropDownType.RADIAL: + return compareValues([ "val", "state" ]); - case DropDownType.HYBRID: - return compareValues([ "val", "state", "cursor" ]); + case DropDownType.HYBRID: + return compareValues([ "val", "state", "cursor" ]); - case DropDownType.SINGLE: - return compareValues([ "val", "state", "dir" ]); + case DropDownType.SINGLE: + return compareValues([ "val", "state", "dir" ]); - default: - return false; + default: + return false; } } diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 56cd5299949..8f977ba2ac0 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -34,6 +34,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { private cursorObj: Phaser.GameObjects.Image; private transitioning: boolean; private transitionCancelled: boolean; + private summaryFinished: boolean; private defaultText: string; private scale: number = 0.1666666667; @@ -126,47 +127,47 @@ export default class EggGachaUiHandler extends MessageUiHandler { gachaInfoContainer.add(gachaUpLabel); switch (gachaType as GachaType) { - case GachaType.LEGENDARY: - if ([ "de", "es" ].includes(currentLanguage)) { - gachaUpLabel.setAlign("center"); - gachaUpLabel.setY(0); - } - if ([ "pt-BR" ].includes(currentLanguage)) { - gachaUpLabel.setX(legendaryLabelX - 2); - } else { - gachaUpLabel.setX(legendaryLabelX); - } - gachaUpLabel.setY(legendaryLabelY); + case GachaType.LEGENDARY: + if ([ "de", "es" ].includes(currentLanguage)) { + gachaUpLabel.setAlign("center"); + gachaUpLabel.setY(0); + } + if ([ "pt-BR" ].includes(currentLanguage)) { + gachaUpLabel.setX(legendaryLabelX - 2); + } else { + gachaUpLabel.setX(legendaryLabelX); + } + gachaUpLabel.setY(legendaryLabelY); - const pokemonIcon = this.scene.add.sprite(pokemonIconX, pokemonIconY, "pokemon_icons_0"); - if ([ "pt-BR" ].includes(currentLanguage)) { - pokemonIcon.setX(pokemonIconX - 2); - } - pokemonIcon.setScale(0.5); - pokemonIcon.setOrigin(0, 0.5); + const pokemonIcon = this.scene.add.sprite(pokemonIconX, pokemonIconY, "pokemon_icons_0"); + if ([ "pt-BR" ].includes(currentLanguage)) { + pokemonIcon.setX(pokemonIconX - 2); + } + pokemonIcon.setScale(0.5); + pokemonIcon.setOrigin(0, 0.5); - gachaInfoContainer.add(pokemonIcon); - break; - case GachaType.MOVE: - if ([ "de", "es", "fr", "pt-BR" ].includes(currentLanguage)) { - gachaUpLabel.setAlign("center"); - gachaUpLabel.setY(0); - } + gachaInfoContainer.add(pokemonIcon); + break; + case GachaType.MOVE: + if ([ "de", "es", "fr", "pt-BR" ].includes(currentLanguage)) { + gachaUpLabel.setAlign("center"); + gachaUpLabel.setY(0); + } - gachaUpLabel.setText(i18next.t("egg:moveUPGacha")); - gachaUpLabel.setX(0); - gachaUpLabel.setOrigin(0.5, 0); - break; - case GachaType.SHINY: - if ([ "de", "fr", "ko" ].includes(currentLanguage)) { - gachaUpLabel.setAlign("center"); - gachaUpLabel.setY(0); - } + gachaUpLabel.setText(i18next.t("egg:moveUPGacha")); + gachaUpLabel.setX(0); + gachaUpLabel.setOrigin(0.5, 0); + break; + case GachaType.SHINY: + if ([ "de", "fr", "ko" ].includes(currentLanguage)) { + gachaUpLabel.setAlign("center"); + gachaUpLabel.setY(0); + } - gachaUpLabel.setText(i18next.t("egg:shinyUPGacha")); - gachaUpLabel.setX(0); - gachaUpLabel.setOrigin(0.5, 0); - break; + gachaUpLabel.setText(i18next.t("egg:shinyUPGacha")); + gachaUpLabel.setX(0); + gachaUpLabel.setOrigin(0.5, 0); + break; } const gachaKnob = this.scene.add.sprite(191, 89, "gacha_knob"); @@ -469,17 +470,22 @@ export default class EggGachaUiHandler extends MessageUiHandler { getGuaranteedEggTierFromPullCount(pullCount: number): EggTier { switch (pullCount) { - case 10: - return EggTier.GREAT; - case 25: - return EggTier.ULTRA; - default: - return EggTier.COMMON; + case 10: + return EggTier.RARE; + case 25: + return EggTier.EPIC; + default: + return EggTier.COMMON; } } showSummary(eggs: Egg[]): void { - this.transitioning = false; + // the overlay will appear faster if the egg pulling animation was skipped + const overlayEaseInDuration = this.getDelayValue(750); + + this.summaryFinished = false; + this.transitionCancelled = false; + this.setTransitioning(true); this.eggGachaSummaryContainer.setVisible(true); const eggScale = eggs.length < 20 ? 1 : 0.5; @@ -488,12 +494,14 @@ export default class EggGachaUiHandler extends MessageUiHandler { targets: this.eggGachaOverlay, alpha: 0.5, ease: "Sine.easeOut", - duration: 750, + duration: overlayEaseInDuration, onComplete: () => { const rowItems = 5; const rows = Math.ceil(eggs.length / rowItems); const cols = Math.min(eggs.length, rowItems); const height = this.eggGachaOverlay.displayHeight - this.eggGachaMessageBox.displayHeight; + + // Create sprites for each egg const eggContainers = eggs.map((egg, t) => { const col = t % rowItems; const row = Math.floor(t / rowItems); @@ -508,21 +516,31 @@ export default class EggGachaUiHandler extends MessageUiHandler { const eggText = addTextObject(this.scene, 0, 14, egg.getEggDescriptor(), TextStyle.PARTY, { align: "center" }); eggText.setOrigin(0.5, 0); - eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.ULTRA)); + eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.EPIC)); ret.add(eggText); this.eggGachaSummaryContainer.addAt(ret, 0); return ret; }); - eggContainers.forEach((eggContainer, e) => { - this.scene.tweens.add({ - targets: eggContainer, - delay: this.getDelayValue(e * 100), - duration: this.getDelayValue(350), - scale: eggScale, - ease: "Sine.easeOut" - }); + // If action/cancel was pressed when the overlay was easing in, show all eggs at once + // Otherwise show the eggs one by one with a small delay between each + eggContainers.forEach((eggContainer, index) => { + const delay = !this.transitionCancelled ? this.getDelayValue(index * 100) : 0; + this.scene.time.delayedCall(delay, () => + this.scene.tweens.add({ + targets: eggContainer, + duration: this.getDelayValue(350), + scale: eggScale, + ease: "Sine.easeOut", + onComplete: () => { + if (index === eggs.length - 1) { + this.setTransitioning(false); + this.summaryFinished = true; + } + } + })); + }); } }); @@ -540,6 +558,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.eggGachaSummaryContainer.setAlpha(1); this.eggGachaSummaryContainer.removeAll(true); this.setTransitioning(false); + this.summaryFinished = false; this.eggGachaOptionsContainer.setVisible(true); } }); @@ -548,11 +567,11 @@ export default class EggGachaUiHandler extends MessageUiHandler { updateGachaInfo(gachaType: GachaType): void { const infoContainer = this.gachaInfoContainers[gachaType]; switch (gachaType as GachaType) { - case GachaType.LEGENDARY: - const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.scene, new Date().getTime())); - const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite; - pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false)); - break; + case GachaType.LEGENDARY: + const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.scene, new Date().getTime())); + const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite; + pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false)); + break; } } @@ -613,112 +632,112 @@ export default class EggGachaUiHandler extends MessageUiHandler { } else { if (this.eggGachaSummaryContainer.visible) { - if (button === Button.ACTION || button === Button.CANCEL) { + if (this.summaryFinished && (button === Button.ACTION || button === Button.CANCEL)) { this.hideSummary(); success = true; } } else { switch (button) { - case Button.ACTION: - switch (this.cursor) { - case 0: - if (!this.scene.gameData.voucherCounts[VoucherType.REGULAR] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 99) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.REGULAR, 1); - } - this.pull(); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); - } - break; - case 2: - if (!this.scene.gameData.voucherCounts[VoucherType.PLUS] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 95) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.PLUS, 1); - } - this.pull(5); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); - } - break; - case 1: - case 3: - if ((this.cursor === 1 && this.scene.gameData.voucherCounts[VoucherType.REGULAR] < 10 && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) + case Button.ACTION: + switch (this.cursor) { + case 0: + if (!this.scene.gameData.voucherCounts[VoucherType.REGULAR] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.REGULAR, 1); + } + this.pull(); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); + } + break; + case 2: + if (!this.scene.gameData.voucherCounts[VoucherType.PLUS] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 95 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.PLUS, 1); + } + this.pull(5); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); + } + break; + case 1: + case 3: + if ((this.cursor === 1 && this.scene.gameData.voucherCounts[VoucherType.REGULAR] < 10 && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) || (this.cursor === 3 && !this.scene.gameData.voucherCounts[VoucherType.PREMIUM] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE)) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 90) { - if (this.cursor === 3) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.PREMIUM, 1); + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 90 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (this.cursor === 3) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.PREMIUM, 1); + } + } else { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.REGULAR, 10); + } + } + this.pull(10); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); } - } else { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.REGULAR, 10); + break; + case 4: + if (!this.scene.gameData.voucherCounts[VoucherType.GOLDEN] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 75 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.GOLDEN, 1); + } + this.pull(25); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); } - } - this.pull(10); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); + break; + case 5: + ui.revertMode(); + success = true; + break; } break; - case 4: - if (!this.scene.gameData.voucherCounts[VoucherType.GOLDEN] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 75) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.GOLDEN, 1); - } - this.pull(25); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); - } - break; - case 5: - ui.revertMode(); + case Button.CANCEL: + this.getUi().revertMode(); success = true; break; - } - break; - case Button.CANCEL: - this.getUi().revertMode(); - success = true; - break; - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.DOWN: - if (this.cursor < 5) { - success = this.setCursor(this.cursor + 1); - } - break; - case Button.LEFT: - if (this.gachaCursor) { - success = this.setGachaCursor(this.gachaCursor - 1); - } - break; - case Button.RIGHT: - if (this.gachaCursor < Utils.getEnumKeys(GachaType).length - 1) { - success = this.setGachaCursor(this.gachaCursor + 1); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.DOWN: + if (this.cursor < 5) { + success = this.setCursor(this.cursor + 1); + } + break; + case Button.LEFT: + if (this.gachaCursor) { + success = this.setGachaCursor(this.gachaCursor - 1); + } + break; + case Button.RIGHT: + if (this.gachaCursor < Utils.getEnumKeys(GachaType).length - 1) { + success = this.setGachaCursor(this.gachaCursor + 1); + } + break; } } } diff --git a/src/ui/egg-summary-ui-handler.ts b/src/ui/egg-summary-ui-handler.ts index 519722b1505..da93168926e 100644 --- a/src/ui/egg-summary-ui-handler.ts +++ b/src/ui/egg-summary-ui-handler.ts @@ -42,6 +42,9 @@ export default class EggSummaryUiHandler extends MessageUiHandler { private scrollGridHandler : ScrollableGridUiHandler; private cursorObj: Phaser.GameObjects.Image; + /** used to add a delay before which it is not possible to exit the summary */ + private blockExit: boolean; + /** * Allows subscribers to listen for events * @@ -168,6 +171,13 @@ export default class EggSummaryUiHandler extends MessageUiHandler { this.setCursor(0); this.scene.playSoundWithoutBgm("evolution_fanfare"); + + // Prevent exiting the egg summary for 2 seconds if the egg hatching + // was skipped automatically and for 1 second otherwise + const exitBlockingDuration = (this.scene.eggSkipPreference === 2) ? 2000 : 1000; + this.blockExit = true; + this.scene.time.delayedCall(exitBlockingDuration, () => this.blockExit = false); + return true; } @@ -203,13 +213,17 @@ export default class EggSummaryUiHandler extends MessageUiHandler { const ui = this.getUi(); let success = false; - const error = false; + let error = false; if (button === Button.CANCEL) { - const phase = this.scene.getCurrentPhase(); - if (phase instanceof EggSummaryPhase) { - phase.end(); + if (!this.blockExit) { + const phase = this.scene.getCurrentPhase(); + if (phase instanceof EggSummaryPhase) { + phase.end(); + } + success = true; + } else { + error = true; } - success = true; } else { this.scrollGridHandler.processInput(button); } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 78894a7bf00..ee6641a1a27 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -152,26 +152,26 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { } } else { switch (button) { - case Button.UP: - if (cursor >= 2) { - success = this.setCursor(cursor - 2); - } - break; - case Button.DOWN: - if (cursor < 2) { - success = this.setCursor(cursor + 2); - } - break; - case Button.LEFT: - if (cursor % 2 === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor % 2 === 0) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor >= 2) { + success = this.setCursor(cursor - 2); + } + break; + case Button.DOWN: + if (cursor < 2) { + success = this.setCursor(cursor + 2); + } + break; + case Button.LEFT: + if (cursor % 2 === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor % 2 === 0) { + success = this.setCursor(cursor + 1); + } + break; } } diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index 4d7b0855092..671bed29036 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -351,16 +351,16 @@ export default class GameStatsUiHandler extends UiHandler { this.scene.ui.revertMode(); } else { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.DOWN: - if (this.cursor < Math.ceil((Object.keys(displayStats).length - 18) / 2)) { - success = this.setCursor(this.cursor + 1); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.DOWN: + if (this.cursor < Math.ceil((Object.keys(displayStats).length - 18) / 2)) { + success = this.setCursor(this.cursor + 1); + } + break; } } diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 192a6cefa7a..8be432ad6c1 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -97,18 +97,18 @@ export default class LoginFormUiHandler extends FormModalUiHandler { error = error.slice(0, colonIndex); } switch (error) { - case this.ERR_USERNAME: - return i18next.t("menu:invalidLoginUsername"); - case this.ERR_PASSWORD: - return i18next.t("menu:invalidLoginPassword"); - case this.ERR_ACCOUNT_EXIST: - return i18next.t("menu:accountNonExistent"); - case this.ERR_PASSWORD_MATCH: - return i18next.t("menu:unmatchingPassword"); - case this.ERR_NO_SAVES: - return i18next.t("menu:noSaves"); - case this.ERR_TOO_MANY_SAVES: - return i18next.t("menu:tooManySaves"); + case this.ERR_USERNAME: + return i18next.t("menu:invalidLoginUsername"); + case this.ERR_PASSWORD: + return i18next.t("menu:invalidLoginPassword"); + case this.ERR_ACCOUNT_EXIST: + return i18next.t("menu:accountNonExistent"); + case this.ERR_PASSWORD_MATCH: + return i18next.t("menu:unmatchingPassword"); + case this.ERR_NO_SAVES: + return i18next.t("menu:noSaves"); + case this.ERR_TOO_MANY_SAVES: + return i18next.t("menu:tooManySaves"); } return super.getReadableErrorMessage(error); diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index 47cc45d8f66..fa1882d0351 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -467,148 +467,148 @@ export default class MenuUiHandler extends MessageUiHandler { } this.showText("", 0); switch (adjustedCursor) { - case MenuOptions.GAME_SETTINGS: - ui.setOverlayMode(Mode.SETTINGS); - success = true; - break; - case MenuOptions.ACHIEVEMENTS: - ui.setOverlayMode(Mode.ACHIEVEMENTS); - success = true; - break; - case MenuOptions.STATS: - ui.setOverlayMode(Mode.GAME_STATS); - success = true; - break; - case MenuOptions.RUN_HISTORY: - ui.setOverlayMode(Mode.RUN_HISTORY); - success = true; - break; - case MenuOptions.EGG_LIST: - if (this.scene.gameData.eggs.length) { + case MenuOptions.GAME_SETTINGS: + ui.setOverlayMode(Mode.SETTINGS); + success = true; + break; + case MenuOptions.ACHIEVEMENTS: + ui.setOverlayMode(Mode.ACHIEVEMENTS); + success = true; + break; + case MenuOptions.STATS: + ui.setOverlayMode(Mode.GAME_STATS); + success = true; + break; + case MenuOptions.RUN_HISTORY: + ui.setOverlayMode(Mode.RUN_HISTORY); + success = true; + break; + case MenuOptions.EGG_LIST: + if (this.scene.gameData.eggs.length) { + ui.revertMode(); + ui.setOverlayMode(Mode.EGG_LIST); + success = true; + } else { + ui.showText(i18next.t("menuUiHandler:noEggs"), null, () => ui.showText(""), Utils.fixedInt(1500)); + error = true; + } + break; + case MenuOptions.EGG_GACHA: ui.revertMode(); - ui.setOverlayMode(Mode.EGG_LIST); + ui.setOverlayMode(Mode.EGG_GACHA); success = true; - } else { - ui.showText(i18next.t("menuUiHandler:noEggs"), null, () => ui.showText(""), Utils.fixedInt(1500)); - error = true; - } - break; - case MenuOptions.EGG_GACHA: - ui.revertMode(); - ui.setOverlayMode(Mode.EGG_GACHA); - success = true; - break; - case MenuOptions.MANAGE_DATA: - if (!bypassLogin && !this.manageDataConfig.options.some(o => o.label === i18next.t("menuUiHandler:linkDiscord") || o.label === i18next.t("menuUiHandler:unlinkDiscord"))) { - this.manageDataConfig.options.splice(this.manageDataConfig.options.length - 1, 0, - { - label: loggedInUser?.discordId === "" ? i18next.t("menuUiHandler:linkDiscord") : i18next.t("menuUiHandler:unlinkDiscord"), - handler: () => { - if (loggedInUser?.discordId === "") { - const token = Utils.getCookie(Utils.sessionIdKey); - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); - const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; - const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&state=${token}&prompt=none`; - window.open(discordUrl, "_self"); - return true; - } else { - Utils.apiPost("/auth/discord/logout", undefined, undefined, true).then(res => { - if (!res.ok) { - console.error(`Unlink failed (${res.status}: ${res.statusText})`); - } - updateUserInfo().then(() => this.scene.reset(true, true)); - }); - return true; + break; + case MenuOptions.MANAGE_DATA: + if (!bypassLogin && !this.manageDataConfig.options.some(o => o.label === i18next.t("menuUiHandler:linkDiscord") || o.label === i18next.t("menuUiHandler:unlinkDiscord"))) { + this.manageDataConfig.options.splice(this.manageDataConfig.options.length - 1, 0, + { + label: loggedInUser?.discordId === "" ? i18next.t("menuUiHandler:linkDiscord") : i18next.t("menuUiHandler:unlinkDiscord"), + handler: () => { + if (loggedInUser?.discordId === "") { + const token = Utils.getCookie(Utils.sessionIdKey); + const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); + const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; + const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&state=${token}&prompt=none`; + window.open(discordUrl, "_self"); + return true; + } else { + Utils.apiPost("/auth/discord/logout", undefined, undefined, true).then(res => { + if (!res.ok) { + console.error(`Unlink failed (${res.status}: ${res.statusText})`); + } + updateUserInfo().then(() => this.scene.reset(true, true)); + }); + return true; + } } - } - }, - { - label: loggedInUser?.googleId === "" ? i18next.t("menuUiHandler:linkGoogle") : i18next.t("menuUiHandler:unlinkGoogle"), - handler: () => { - if (loggedInUser?.googleId === "") { - const token = Utils.getCookie(Utils.sessionIdKey); - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); - const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; - const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&response_type=code&redirect_uri=${redirectUri}&scope=openid&state=${token}`; - window.open(googleUrl, "_self"); - return true; - } else { - Utils.apiPost("/auth/google/logout", undefined, undefined, true).then(res => { - if (!res.ok) { - console.error(`Unlink failed (${res.status}: ${res.statusText})`); - } - updateUserInfo().then(() => this.scene.reset(true, true)); - }); - return true; + }, + { + label: loggedInUser?.googleId === "" ? i18next.t("menuUiHandler:linkGoogle") : i18next.t("menuUiHandler:unlinkGoogle"), + handler: () => { + if (loggedInUser?.googleId === "") { + const token = Utils.getCookie(Utils.sessionIdKey); + const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); + const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; + const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&response_type=code&redirect_uri=${redirectUri}&scope=openid&state=${token}`; + window.open(googleUrl, "_self"); + return true; + } else { + Utils.apiPost("/auth/google/logout", undefined, undefined, true).then(res => { + if (!res.ok) { + console.error(`Unlink failed (${res.status}: ${res.statusText})`); + } + updateUserInfo().then(() => this.scene.reset(true, true)); + }); + return true; + } } - } - }); - } - ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.manageDataConfig); - success = true; - break; - case MenuOptions.COMMUNITY: - ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.communityConfig); - success = true; - break; - case MenuOptions.SAVE_AND_QUIT: - if (this.scene.currentBattle) { + }); + } + ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.manageDataConfig); success = true; - const doSaveQuit = () => { - ui.setMode(Mode.LOADING, { - buttonActions: [], fadeOut: () => - this.scene.gameData.saveAll(this.scene, true, true, true, true).then(() => { + break; + case MenuOptions.COMMUNITY: + ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.communityConfig); + success = true; + break; + case MenuOptions.SAVE_AND_QUIT: + if (this.scene.currentBattle) { + success = true; + const doSaveQuit = () => { + ui.setMode(Mode.LOADING, { + buttonActions: [], fadeOut: () => + this.scene.gameData.saveAll(this.scene, true, true, true, true).then(() => { - this.scene.reset(true); - }) + this.scene.reset(true); + }) + }); + }; + if (this.scene.currentBattle.turn > 1) { + ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { + if (!this.active) { + this.showText("", 0); + return; + } + ui.setOverlayMode(Mode.CONFIRM, doSaveQuit, () => { + ui.revertMode(); + this.showText("", 0); + }, false, this.menuBg.width * -1); + }); + } else { + doSaveQuit(); + } + } else { + error = true; + } + break; + case MenuOptions.LOG_OUT: + success = true; + const doLogout = () => { + ui.setMode(Mode.LOADING, { + buttonActions: [], fadeOut: () => Utils.apiFetch("account/logout", true).then(res => { + if (!res.ok) { + console.error(`Log out failed (${res.status}: ${res.statusText})`); + } + Utils.removeCookie(Utils.sessionIdKey); + updateUserInfo().then(() => this.scene.reset(true, true)); + }) }); }; - if (this.scene.currentBattle.turn > 1) { + if (this.scene.currentBattle) { ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { if (!this.active) { this.showText("", 0); return; } - ui.setOverlayMode(Mode.CONFIRM, doSaveQuit, () => { + ui.setOverlayMode(Mode.CONFIRM, doLogout, () => { ui.revertMode(); this.showText("", 0); }, false, this.menuBg.width * -1); }); } else { - doSaveQuit(); + doLogout(); } - } else { - error = true; - } - break; - case MenuOptions.LOG_OUT: - success = true; - const doLogout = () => { - ui.setMode(Mode.LOADING, { - buttonActions: [], fadeOut: () => Utils.apiFetch("account/logout", true).then(res => { - if (!res.ok) { - console.error(`Log out failed (${res.status}: ${res.statusText})`); - } - Utils.removeCookie(Utils.sessionIdKey); - updateUserInfo().then(() => this.scene.reset(true, true)); - }) - }); - }; - if (this.scene.currentBattle) { - ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { - if (!this.active) { - this.showText("", 0); - return; - } - ui.setOverlayMode(Mode.CONFIRM, doLogout, () => { - ui.revertMode(); - this.showText("", 0); - }, false, this.menuBg.width * -1); - }); - } else { - doLogout(); - } - break; + break; } } else if (button === Button.CANCEL) { success = true; @@ -619,20 +619,20 @@ export default class MenuUiHandler extends MessageUiHandler { }); } else { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else { - success = this.setCursor(this.menuOptions.length - 1); - } - break; - case Button.DOWN: - if (this.cursor + 1 < this.menuOptions.length) { - success = this.setCursor(this.cursor + 1); - } else { - success = this.setCursor(0); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else { + success = this.setCursor(this.menuOptions.length - 1); + } + break; + case Button.DOWN: + if (this.cursor + 1 < this.menuOptions.length) { + success = this.setCursor(this.cursor + 1); + } else { + success = this.setCursor(0); + } + break; } } diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index ccae95b50ff..fa17a7b1a7f 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -57,18 +57,18 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { let actionMatch: RegExpExecArray | null; while ((actionMatch = actionPattern.exec(text))) { switch (actionMatch[1]) { - case "c": - charVarMap.set(actionMatch.index, actionMatch[2]); - break; - case "d": - delayMap.set(actionMatch.index, parseInt(actionMatch[2])); - break; - case "s": - soundMap.set(actionMatch.index, actionMatch[2]); - break; - case "f": - fadeMap.set(actionMatch.index, parseInt(actionMatch[2])); - break; + case "c": + charVarMap.set(actionMatch.index, actionMatch[2]); + break; + case "d": + delayMap.set(actionMatch.index, parseInt(actionMatch[2])); + break; + case "s": + soundMap.set(actionMatch.index, actionMatch[2]); + break; + case "f": + fadeMap.set(actionMatch.index, parseInt(actionMatch[2])); + break; } text = text.slice(0, actionMatch.index) + text.slice(actionMatch.index + actionMatch[2].length + 4); } diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index f7e57b53193..3f89ebe415f 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -16,7 +16,10 @@ import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import { IntegerHolder } from "./../utils"; import Phaser from "phaser"; -export const SHOP_OPTIONS_ROW_LIMIT = 6; +export const SHOP_OPTIONS_ROW_LIMIT = 7; +const SINGLE_SHOP_ROW_YOFFSET = 12; +const DOUBLE_SHOP_ROW_YOFFSET = 24; +const OPTION_BUTTON_YPOSITION = -62; export default class ModifierSelectUiHandler extends AwaitableUiHandler { private modifierContainer: Phaser.GameObjects.Container; @@ -68,7 +71,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.checkButtonWidth = context.measureText(i18next.t("modifierSelectUiHandler:checkTeam")).width; } - this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, -64); + this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, OPTION_BUTTON_YPOSITION); this.transferButtonContainer.setName("transfer-btn"); this.transferButtonContainer.setVisible(false); ui.add(this.transferButtonContainer); @@ -78,7 +81,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { transferButtonText.setOrigin(1, 0); this.transferButtonContainer.add(transferButtonText); - this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, -64); + this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, OPTION_BUTTON_YPOSITION); this.checkButtonContainer.setName("use-btn"); this.checkButtonContainer.setVisible(false); ui.add(this.checkButtonContainer); @@ -88,7 +91,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { checkButtonText.setOrigin(1, 0); this.checkButtonContainer.add(checkButtonText); - this.rerollButtonContainer = this.scene.add.container(16, -64); + this.rerollButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION); this.rerollButtonContainer.setName("reroll-brn"); this.rerollButtonContainer.setVisible(false); ui.add(this.rerollButtonContainer); @@ -104,7 +107,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.rerollCostText.setPositionRelative(rerollButtonText, rerollButtonText.displayWidth + 5, 1); this.rerollButtonContainer.add(this.rerollCostText); - this.lockRarityButtonContainer = this.scene.add.container(16, -64); + this.lockRarityButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION); this.lockRarityButtonContainer.setVisible(false); ui.add(this.lockRarityButtonContainer); @@ -191,7 +194,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const shopTypeOptions = !removeHealShop ? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, baseShopCost.value) : []; - const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24; + const optionsYOffset = shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET; for (let m = 0; m < typeOptions.length; m++) { const sliceWidth = (this.scene.game.canvas.width / 6) / (typeOptions.length + 2); @@ -211,8 +214,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const row = m < SHOP_OPTIONS_ROW_LIMIT ? 0 : 1; const col = m < SHOP_OPTIONS_ROW_LIMIT ? m : m - SHOP_OPTIONS_ROW_LIMIT; const rowOptions = shopTypeOptions.slice(row ? SHOP_OPTIONS_ROW_LIMIT : 0, row ? undefined : SHOP_OPTIONS_ROW_LIMIT); - const sliceWidth = (this.scene.game.canvas.width / SHOP_OPTIONS_ROW_LIMIT) / (rowOptions.length + 2); - const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (40 - (28 * row - 1))), shopTypeOptions[m]); + const sliceWidth = (this.scene.game.canvas.width / 6) / (rowOptions.length + 2); + const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (42 - (28 * row - 1))), shopTypeOptions[m]); option.setScale(0.375); this.scene.add.existing(option); this.modifierContainer.add(option); @@ -354,79 +357,79 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } } else { switch (button) { - case Button.UP: - if (this.rowCursor === 0 && this.cursor === 3) { - success = this.setCursor(0); - } else if (this.rowCursor < this.shopOptionsRows.length + 1) { - success = this.setRowCursor(this.rowCursor + 1); - } - break; - case Button.DOWN: - if (this.rowCursor) { - success = this.setRowCursor(this.rowCursor - 1); - } else if (this.lockRarityButtonContainer.visible && this.cursor === 0) { - success = this.setCursor(3); - } - break; - case Button.LEFT: - if (!this.rowCursor) { - switch (this.cursor) { - case 0: - success = false; - break; - case 1: - if (this.lockRarityButtonContainer.visible) { - success = this.setCursor(3); - } else { - success = this.rerollButtonContainer.visible && this.setCursor(0); - } - break; - case 2: - if (this.transferButtonContainer.visible) { - success = this.setCursor(1); - } else if (this.rerollButtonContainer.visible) { - success = this.setCursor(0); - } else { - success = false; - } - break; + case Button.UP: + if (this.rowCursor === 0 && this.cursor === 3) { + success = this.setCursor(0); + } else if (this.rowCursor < this.shopOptionsRows.length + 1) { + success = this.setRowCursor(this.rowCursor + 1); } - } else if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else if (this.rowCursor === 1 && this.rerollButtonContainer.visible) { - success = this.setRowCursor(0); - } - break; - case Button.RIGHT: - if (!this.rowCursor) { - switch (this.cursor) { - case 0: - if (this.transferButtonContainer.visible) { - success = this.setCursor(1); - } else { - success = this.setCursor(2); - } - break; - case 1: - success = this.setCursor(2); - break; - case 2: - success = false; - break; - case 3: - if (this.transferButtonContainer.visible) { - success = this.setCursor(1); - } else { - success = this.setCursor(2); - } - break; + break; + case Button.DOWN: + if (this.rowCursor) { + success = this.setRowCursor(this.rowCursor - 1); + } else if (this.lockRarityButtonContainer.visible && this.cursor === 0) { + success = this.setCursor(3); } - } else if (this.cursor < this.getRowItems(this.rowCursor) - 1) { - success = this.setCursor(this.cursor + 1); - } else if (this.rowCursor === 1 && this.transferButtonContainer.visible) { - success = this.setRowCursor(0); - } - break; + break; + case Button.LEFT: + if (!this.rowCursor) { + switch (this.cursor) { + case 0: + success = false; + break; + case 1: + if (this.lockRarityButtonContainer.visible) { + success = this.setCursor(3); + } else { + success = this.rerollButtonContainer.visible && this.setCursor(0); + } + break; + case 2: + if (this.transferButtonContainer.visible) { + success = this.setCursor(1); + } else if (this.rerollButtonContainer.visible) { + success = this.setCursor(0); + } else { + success = false; + } + break; + } + } else if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else if (this.rowCursor === 1 && this.rerollButtonContainer.visible) { + success = this.setRowCursor(0); + } + break; + case Button.RIGHT: + if (!this.rowCursor) { + switch (this.cursor) { + case 0: + if (this.transferButtonContainer.visible) { + success = this.setCursor(1); + } else { + success = this.setCursor(2); + } + break; + case 1: + success = this.setCursor(2); + break; + case 2: + success = false; + break; + case 3: + if (this.transferButtonContainer.visible) { + success = this.setCursor(1); + } else { + success = this.setCursor(2); + } + break; + } + } else if (this.cursor < this.getRowItems(this.rowCursor) - 1) { + success = this.setCursor(this.cursor + 1); + } else if (this.rowCursor === 1 && this.transferButtonContainer.visible) { + success = this.setRowCursor(0); + } + break; } } @@ -456,16 +459,18 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { if (this.rowCursor === 1 && options.length === 0) { // Continue button when no shop items this.cursorObj.setScale(1.25); - this.cursorObj.setPosition((this.scene.game.canvas.width / 18) + 23, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? 6 : 22)); + this.cursorObj.setPosition((this.scene.game.canvas.width / 18) + 23, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2)); ui.showText(i18next.t("modifierSelectUiHandler:continueNextWaveDescription")); return ret; } const sliceWidth = (this.scene.game.canvas.width / 6) / (options.length + 2); if (this.rowCursor < 2) { - this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? 6 : 22)); + // Cursor on free items + this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2)); } else { - this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-16 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1)))); + // Cursor on paying items + this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-14 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1)))); } const type = options[this.cursor].modifierTypeOption.type; @@ -475,16 +480,16 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.moveInfoOverlay.show(allMoves[type.moveId]); } } else if (cursor === 0) { - this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? -72 : -60); + this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? OPTION_BUTTON_YPOSITION - 8 : OPTION_BUTTON_YPOSITION + 4); ui.showText(i18next.t("modifierSelectUiHandler:rerollDesc")); } else if (cursor === 1) { - this.cursorObj.setPosition((this.scene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, -60); + this.cursorObj.setPosition((this.scene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, OPTION_BUTTON_YPOSITION + 4); ui.showText(i18next.t("modifierSelectUiHandler:transferDesc")); } else if (cursor === 2) { - this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, -60); + this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, OPTION_BUTTON_YPOSITION + 4); ui.showText(i18next.t("modifierSelectUiHandler:checkTeamDesc")); } else { - this.cursorObj.setPosition(6, -60); + this.cursorObj.setPosition(6, OPTION_BUTTON_YPOSITION + 4); ui.showText(i18next.t("modifierSelectUiHandler:lockRaritiesDesc")); } @@ -522,12 +527,12 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { private getRowItems(rowCursor: integer): integer { switch (rowCursor) { - case 0: - return 3; - case 1: - return this.options.length; - default: - return this.shopOptionsRows[this.shopOptionsRows.length - (rowCursor - 1)].length; + case 0: + return 3; + case 1: + return this.options.length; + default: + return this.shopOptionsRows[this.shopOptionsRows.length - (rowCursor - 1)].length; } } diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 0a838707432..b568f8b71de 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -159,16 +159,16 @@ export default class MysteryEncounterUiHandler extends UiHandler { } } else { switch (this.optionsContainer.getAll()?.length) { - default: - case 3: - success = this.handleTwoOptionMoveInput(button); - break; - case 4: - success = this.handleThreeOptionMoveInput(button); - break; - case 5: - success = this.handleFourOptionMoveInput(button); - break; + default: + case 3: + success = this.handleTwoOptionMoveInput(button); + break; + case 4: + success = this.handleThreeOptionMoveInput(button); + break; + case 5: + success = this.handleFourOptionMoveInput(button); + break; } this.displayOptionTooltip(); @@ -185,26 +185,26 @@ export default class MysteryEncounterUiHandler extends UiHandler { let success = false; const cursor = this.getCursor(); switch (button) { - case Button.UP: - if (cursor < this.viewPartyIndex) { - success = this.setCursor(this.viewPartyIndex); - } - break; - case Button.DOWN: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } - break; - case Button.LEFT: - if (cursor > 0) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor < this.viewPartyIndex) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor < this.viewPartyIndex) { + success = this.setCursor(this.viewPartyIndex); + } + break; + case Button.DOWN: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } + break; + case Button.LEFT: + if (cursor > 0) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor < this.viewPartyIndex) { + success = this.setCursor(cursor + 1); + } + break; } return success; @@ -214,34 +214,34 @@ export default class MysteryEncounterUiHandler extends UiHandler { let success = false; const cursor = this.getCursor(); switch (button) { - case Button.UP: - if (cursor === 2) { - success = this.setCursor(cursor - 2); - } else { - success = this.setCursor(this.viewPartyIndex); - } - break; - case Button.DOWN: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } else { - success = this.setCursor(2); - } - break; - case Button.LEFT: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } else if (cursor === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor === 1) { - success = this.setCursor(this.viewPartyIndex); - } else if (cursor < 1) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor === 2) { + success = this.setCursor(cursor - 2); + } else { + success = this.setCursor(this.viewPartyIndex); + } + break; + case Button.DOWN: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } else { + success = this.setCursor(2); + } + break; + case Button.LEFT: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } else if (cursor === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor === 1) { + success = this.setCursor(this.viewPartyIndex); + } else if (cursor < 1) { + success = this.setCursor(cursor + 1); + } + break; } return success; @@ -251,34 +251,34 @@ export default class MysteryEncounterUiHandler extends UiHandler { let success = false; const cursor = this.getCursor(); switch (button) { - case Button.UP: - if (cursor >= 2 && cursor !== this.viewPartyIndex) { - success = this.setCursor(cursor - 2); - } else { - success = this.setCursor(this.viewPartyIndex); - } - break; - case Button.DOWN: - if (cursor <= 1) { - success = this.setCursor(cursor + 2); - } else if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } - break; - case Button.LEFT: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } else if (cursor % 2 === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor === 1) { - success = this.setCursor(this.viewPartyIndex); - } else if (cursor % 2 === 0 && cursor !== this.viewPartyIndex) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor >= 2 && cursor !== this.viewPartyIndex) { + success = this.setCursor(cursor - 2); + } else { + success = this.setCursor(this.viewPartyIndex); + } + break; + case Button.DOWN: + if (cursor <= 1) { + success = this.setCursor(cursor + 2); + } else if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } + break; + case Button.LEFT: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } else if (cursor % 2 === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor === 1) { + success = this.setCursor(this.viewPartyIndex); + } else if (cursor % 2 === 0 && cursor !== this.viewPartyIndex) { + success = this.setCursor(cursor + 1); + } + break; } return success; @@ -351,16 +351,16 @@ export default class MysteryEncounterUiHandler extends UiHandler { let optionText: BBCodeText; switch (this.encounterOptions.length) { - default: - case 2: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); - break; - case 3: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); - break; - case 4: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); - break; + default: + case 2: + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + break; + case 3: + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + break; + case 4: + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + break; } this.optionsMeetsReqs.push(option.meetsRequirements(this.scene)); diff --git a/src/ui/outdated-modal-ui-handler.ts b/src/ui/outdated-modal-ui-handler.ts deleted file mode 100644 index fc4b93f9b8a..00000000000 --- a/src/ui/outdated-modal-ui-handler.ts +++ /dev/null @@ -1,47 +0,0 @@ -import BattleScene from "../battle-scene"; -import { ModalConfig, ModalUiHandler } from "./modal-ui-handler"; -import { addTextObject, TextStyle } from "./text"; -import { Mode } from "./ui"; - -export default class OutdatedModalUiHandler extends ModalUiHandler { - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); - } - - getModalTitle(): string { - return ""; - } - - getWidth(): number { - return 160; - } - - getHeight(): number { - return 64; - } - - getMargin(): [number, number, number, number] { - return [ 0, 0, 48, 0 ]; - } - - getButtonLabels(): string[] { - return [ ]; - } - - setup(): void { - super.setup(); - - const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, "Your client is currently outdated.\nPlease reload to update the game.\n\nIf this error persists, please clear your browser cache.", TextStyle.WINDOW, { fontSize: "48px", align: "center" }); - label.setOrigin(0.5, 0.5); - - this.modalContainer.add(label); - } - - show(args: any[]): boolean { - const config: ModalConfig = { - buttonActions: [] - }; - - return super.show([ config ]); - } -} diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index e7c1b02cf01..e96fde8d54f 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -539,42 +539,42 @@ export default class PartyUiHandler extends MessageUiHandler { return true; } else { switch (button) { - case Button.LEFT: + case Button.LEFT: /** Decrease quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - this.transferQuantities[option] = this.transferQuantities[option] === 1 ? this.transferQuantitiesMax[option] : this.transferQuantities[option] - 1; - this.updateOptions(); - success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ - } - break; - case Button.RIGHT: + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = this.transferQuantities[option] === 1 ? this.transferQuantitiesMax[option] : this.transferQuantities[option] - 1; + this.updateOptions(); + success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ + } + break; + case Button.RIGHT: /** Increase quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - this.transferQuantities[option] = this.transferQuantities[option] === this.transferQuantitiesMax[option] ? 1 : this.transferQuantities[option] + 1; - this.updateOptions(); - success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ - } - break; - case Button.UP: - /** If currently selecting items to transfer, reset quantity selection */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - if (option !== PartyOption.ALL) { - this.transferQuantities[option] = this.transferQuantitiesMax[option]; + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = this.transferQuantities[option] === this.transferQuantitiesMax[option] ? 1 : this.transferQuantities[option] + 1; + this.updateOptions(); + success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ } - this.updateOptions(); - } - success = this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1); /** Move cursor */ - break; - case Button.DOWN: + break; + case Button.UP: /** If currently selecting items to transfer, reset quantity selection */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - if (option !== PartyOption.ALL) { - this.transferQuantities[option] = this.transferQuantitiesMax[option]; + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (option !== PartyOption.ALL) { + this.transferQuantities[option] = this.transferQuantitiesMax[option]; + } + this.updateOptions(); } - this.updateOptions(); - } - success = this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); /** Move cursor */ - break; + success = this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1); /** Move cursor */ + break; + case Button.DOWN: + /** If currently selecting items to transfer, reset quantity selection */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (option !== PartyOption.ALL) { + this.transferQuantities[option] = this.transferQuantitiesMax[option]; + } + this.updateOptions(); + } + success = this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); /** Move cursor */ + break; } // show move description @@ -631,28 +631,28 @@ export default class PartyUiHandler extends MessageUiHandler { const battlerCount = this.scene.currentBattle.getBattlerCount(); switch (button) { - case Button.UP: - success = this.setCursor(this.cursor ? this.cursor < 6 ? this.cursor - 1 : slotCount - 1 : 6); - break; - case Button.DOWN: - success = this.setCursor(this.cursor < 6 ? this.cursor < slotCount - 1 ? this.cursor + 1 : 6 : 0); - break; - case Button.LEFT: - if (this.cursor >= battlerCount && this.cursor <= 6) { - success = this.setCursor(0); - } - break; - case Button.RIGHT: - if (slotCount === battlerCount) { - success = this.setCursor(6); + case Button.UP: + success = this.setCursor(this.cursor ? this.cursor < 6 ? this.cursor - 1 : slotCount - 1 : 6); break; - } else if (battlerCount >= 2 && slotCount > battlerCount && this.getCursor() === 0 && this.lastCursor === 1) { - success = this.setCursor(2); + case Button.DOWN: + success = this.setCursor(this.cursor < 6 ? this.cursor < slotCount - 1 ? this.cursor + 1 : 6 : 0); break; - } else if (slotCount > battlerCount && this.cursor < battlerCount) { - success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount); + case Button.LEFT: + if (this.cursor >= battlerCount && this.cursor <= 6) { + success = this.setCursor(0); + } break; - } + case Button.RIGHT: + if (slotCount === battlerCount) { + success = this.setCursor(6); + break; + } else if (battlerCount >= 2 && slotCount > battlerCount && this.getCursor() === 0 && this.lastCursor === 1) { + success = this.setCursor(2); + break; + } else if (slotCount > battlerCount && this.cursor < battlerCount) { + success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount); + break; + } } } @@ -671,6 +671,9 @@ export default class PartyUiHandler extends MessageUiHandler { } else if (this.cursor === 6) { this.partyCancelButton.select(); } + if (this.lastCursor < 6 && this.lastCursor >= party.length) { + this.lastCursor = party.length - 1; + } for (const p in party) { const slotIndex = parseInt(p); @@ -770,19 +773,19 @@ export default class PartyUiHandler extends MessageUiHandler { let optionsMessage = i18next.t("partyUiHandler:doWhatWithThisPokemon"); switch (this.partyUiMode) { - case PartyUiMode.MOVE_MODIFIER: - optionsMessage = i18next.t("partyUiHandler:selectAMove"); - break; - case PartyUiMode.MODIFIER_TRANSFER: - if (!this.transferMode) { - optionsMessage = i18next.t("partyUiHandler:changeQuantity"); - } - break; - case PartyUiMode.SPLICE: - if (!this.transferMode) { - optionsMessage = i18next.t("partyUiHandler:selectAnotherPokemonToSplice"); - } - break; + case PartyUiMode.MOVE_MODIFIER: + optionsMessage = i18next.t("partyUiHandler:selectAMove"); + break; + case PartyUiMode.MODIFIER_TRANSFER: + if (!this.transferMode) { + optionsMessage = i18next.t("partyUiHandler:changeQuantity"); + } + break; + case PartyUiMode.SPLICE: + if (!this.transferMode) { + optionsMessage = i18next.t("partyUiHandler:selectAnotherPokemonToSplice"); + } + break; } this.showText(optionsMessage, 0); @@ -826,64 +829,64 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.partyUiMode !== PartyUiMode.MOVE_MODIFIER && this.partyUiMode !== PartyUiMode.REMEMBER_MOVE_MODIFIER && (this.transferMode || this.partyUiMode !== PartyUiMode.MODIFIER_TRANSFER)) { switch (this.partyUiMode) { - case PartyUiMode.SWITCH: - case PartyUiMode.FAINT_SWITCH: - case PartyUiMode.POST_BATTLE_SWITCH: - if (this.cursor >= this.scene.currentBattle.getBattlerCount()) { - const allowBatonModifierSwitch = + case PartyUiMode.SWITCH: + case PartyUiMode.FAINT_SWITCH: + case PartyUiMode.POST_BATTLE_SWITCH: + if (this.cursor >= this.scene.currentBattle.getBattlerCount()) { + const allowBatonModifierSwitch = this.partyUiMode !== PartyUiMode.FAINT_SWITCH && this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === this.scene.getPlayerField()[this.fieldIndex].id); - const moveHistory = this.scene.getPlayerField()[this.fieldIndex].getMoveHistory(); - const isBatonPassMove = this.partyUiMode === PartyUiMode.FAINT_SWITCH && moveHistory.length && allMoves[moveHistory[moveHistory.length - 1].move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() && moveHistory[moveHistory.length - 1].result === MoveResult.SUCCESS; + const moveHistory = this.scene.getPlayerField()[this.fieldIndex].getMoveHistory(); + const isBatonPassMove = this.partyUiMode === PartyUiMode.FAINT_SWITCH && moveHistory.length && allMoves[moveHistory[moveHistory.length - 1].move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() && moveHistory[moveHistory.length - 1].result === MoveResult.SUCCESS; - // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true - // at the same time, because they both explicitly check for a mutually - // exclusive partyUiMode. But better safe than sorry. - this.options.push(isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT); - if (allowBatonModifierSwitch && !isBatonPassMove) { + // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true + // at the same time, because they both explicitly check for a mutually + // exclusive partyUiMode. But better safe than sorry. + this.options.push(isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT); + if (allowBatonModifierSwitch && !isBatonPassMove) { // the BATON modifier gives an extra switch option for // pokemon-command switches, allowing buffs to be optionally passed - this.options.push(PartyOption.PASS_BATON); + this.options.push(PartyOption.PASS_BATON); + } } - } - break; - case PartyUiMode.REVIVAL_BLESSING: - this.options.push(PartyOption.REVIVE); - break; - case PartyUiMode.MODIFIER: - this.options.push(PartyOption.APPLY); - break; - case PartyUiMode.TM_MODIFIER: - this.options.push(PartyOption.TEACH); - break; - case PartyUiMode.MODIFIER_TRANSFER: - this.options.push(PartyOption.TRANSFER); - break; - case PartyUiMode.SPLICE: - if (this.transferMode) { - if (this.cursor !== this.transferCursor) { - this.options.push(PartyOption.SPLICE); - } - } else { + break; + case PartyUiMode.REVIVAL_BLESSING: + this.options.push(PartyOption.REVIVE); + break; + case PartyUiMode.MODIFIER: this.options.push(PartyOption.APPLY); - } - break; - case PartyUiMode.RELEASE: - this.options.push(PartyOption.RELEASE); - break; - case PartyUiMode.CHECK: - if (this.scene.getCurrentPhase() instanceof SelectModifierPhase) { - formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - for (let i = 0; i < formChangeItemModifiers.length; i++) { - this.options.push(PartyOption.FORM_CHANGE_ITEM + i); + break; + case PartyUiMode.TM_MODIFIER: + this.options.push(PartyOption.TEACH); + break; + case PartyUiMode.MODIFIER_TRANSFER: + this.options.push(PartyOption.TRANSFER); + break; + case PartyUiMode.SPLICE: + if (this.transferMode) { + if (this.cursor !== this.transferCursor) { + this.options.push(PartyOption.SPLICE); + } + } else { + this.options.push(PartyOption.APPLY); } - } - break; - case PartyUiMode.SELECT: - this.options.push(PartyOption.SELECT); - break; + break; + case PartyUiMode.RELEASE: + this.options.push(PartyOption.RELEASE); + break; + case PartyUiMode.CHECK: + if (this.scene.getCurrentPhase() instanceof SelectModifierPhase) { + formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); + for (let i = 0; i < formChangeItemModifiers.length; i++) { + this.options.push(PartyOption.FORM_CHANGE_ITEM + i); + } + } + break; + case PartyUiMode.SELECT: + this.options.push(PartyOption.SELECT); + break; } this.options.push(PartyOption.SUMMARY); @@ -959,33 +962,33 @@ export default class PartyUiHandler extends MessageUiHandler { optionName = "↓"; } else if ((this.partyUiMode !== PartyUiMode.REMEMBER_MOVE_MODIFIER && (this.partyUiMode !== PartyUiMode.MODIFIER_TRANSFER || this.transferMode)) || option === PartyOption.CANCEL) { switch (option) { - case PartyOption.MOVE_1: - case PartyOption.MOVE_2: - case PartyOption.MOVE_3: - case PartyOption.MOVE_4: - const move = pokemon.moveset[option - PartyOption.MOVE_1]!; // TODO: is the bang correct? - if (this.showMovePp) { - const maxPP = move.getMovePp(); - const currPP = maxPP - move.ppUsed; - optionName = `${move.getName()} ${currPP}/${maxPP}`; - } else { - optionName = move.getName(); - } - break; - default: - if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) { - const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; - optionName = `${modifier.active ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${modifier.type.name}`; - } else if (option === PartyOption.UNPAUSE_EVOLUTION) { - optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:UNPAUSE_EVOLUTION") : i18next.t("partyUiHandler:PAUSE_EVOLUTION")}`; - } else { - if (this.localizedOptions.includes(option)) { - optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`); + case PartyOption.MOVE_1: + case PartyOption.MOVE_2: + case PartyOption.MOVE_3: + case PartyOption.MOVE_4: + const move = pokemon.moveset[option - PartyOption.MOVE_1]!; // TODO: is the bang correct? + if (this.showMovePp) { + const maxPP = move.getMovePp(); + const currPP = maxPP - move.ppUsed; + optionName = `${move.getName()} ${currPP}/${maxPP}`; } else { - optionName = Utils.toReadableString(PartyOption[option]); + optionName = move.getName(); } - } - break; + break; + default: + if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) { + const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; + optionName = `${modifier.active ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${modifier.type.name}`; + } else if (option === PartyOption.UNPAUSE_EVOLUTION) { + optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:UNPAUSE_EVOLUTION") : i18next.t("partyUiHandler:PAUSE_EVOLUTION")}`; + } else { + if (this.localizedOptions.includes(option)) { + optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`); + } else { + optionName = Utils.toReadableString(PartyOption[option]); + } + } + break; } } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { const move = learnableLevelMoves[option]; diff --git a/src/ui/pokemon-icon-anim-handler.ts b/src/ui/pokemon-icon-anim-handler.ts index d6796d5cb5d..c7a24f69200 100644 --- a/src/ui/pokemon-icon-anim-handler.ts +++ b/src/ui/pokemon-icon-anim-handler.ts @@ -39,12 +39,12 @@ export default class PokemonIconAnimHandler { getModeYDelta(mode: PokemonIconAnimMode): number { switch (mode) { - case PokemonIconAnimMode.NONE: - return 0; - case PokemonIconAnimMode.PASSIVE: - return -1; - case PokemonIconAnimMode.ACTIVE: - return -2; + case PokemonIconAnimMode.NONE: + return 0; + case PokemonIconAnimMode.PASSIVE: + return -1; + case PokemonIconAnimMode.ACTIVE: + return -2; } } diff --git a/src/ui/registration-form-ui-handler.ts b/src/ui/registration-form-ui-handler.ts index c94d060c0b7..2f8486bcdb3 100644 --- a/src/ui/registration-form-ui-handler.ts +++ b/src/ui/registration-form-ui-handler.ts @@ -50,12 +50,12 @@ export default class RegistrationFormUiHandler extends FormModalUiHandler { error = error.slice(0, colonIndex); } switch (error) { - case "invalid username": - return i18next.t("menu:invalidRegisterUsername"); - case "invalid password": - return i18next.t("menu:invalidRegisterPassword"); - case "failed to add account record": - return i18next.t("menu:usernameAlreadyUsed"); + case "invalid username": + return i18next.t("menu:invalidRegisterUsername"); + case "invalid password": + return i18next.t("menu:invalidRegisterPassword"); + case "failed to add account record": + return i18next.t("menu:usernameAlreadyUsed"); } return super.getReadableErrorMessage(error); diff --git a/src/ui/run-history-ui-handler.ts b/src/ui/run-history-ui-handler.ts index f4de9b21963..061f15d0956 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -12,6 +12,7 @@ import { BattleType } from "../battle"; import { RunEntry } from "../system/game-data"; import { PlayerGender } from "#enums/player-gender"; import { TrainerVariant } from "../field/trainer"; +import { RunDisplayMode } from "#app/ui/run-info-ui-handler"; export type RunSelectCallback = (cursor: number) => void; @@ -104,7 +105,7 @@ export default class RunHistoryUiHandler extends MessageUiHandler { if (button === Button.ACTION) { const cursor = this.cursor + this.scrollCursor; if (this.runs[cursor]) { - this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.runs[cursor].entryData, true); + this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.runs[cursor].entryData, RunDisplayMode.RUN_HISTORY, true); } else { return false; } @@ -117,28 +118,28 @@ export default class RunHistoryUiHandler extends MessageUiHandler { } } else if (this.runs.length > 0) { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1); - } else if (this.runs.length > 1) { + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1); + } else if (this.runs.length > 1) { // wrap around to the bottom - success = this.setCursor(Math.min(this.runs.length - 1, this.maxRows - 1)); - success = this.setScrollCursor(Math.max(0, this.runs.length - this.maxRows)) || success; - } - break; - case Button.DOWN: - if (this.cursor < Math.min(this.maxRows - 1, this.runs.length - this.scrollCursor - 1)) { - success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.runs.length - this.maxRows) { - success = this.setScrollCursor(this.scrollCursor + 1); - } else if (this.runs.length > 1) { + success = this.setCursor(Math.min(this.runs.length - 1, this.maxRows - 1)); + success = this.setScrollCursor(Math.max(0, this.runs.length - this.maxRows)) || success; + } + break; + case Button.DOWN: + if (this.cursor < Math.min(this.maxRows - 1, this.runs.length - this.scrollCursor - 1)) { + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.runs.length - this.maxRows) { + success = this.setScrollCursor(this.scrollCursor + 1); + } else if (this.runs.length > 1) { // wrap around to the top - success = this.setCursor(0); - success = this.setScrollCursor(0) || success; - } - break; + success = this.setCursor(0); + success = this.setScrollCursor(0) || success; + } + break; } } @@ -332,19 +333,19 @@ class RunEntryContainer extends Phaser.GameObjects.Container { const gameModeLabel = addTextObject(this.scene, 8, 19, "", TextStyle.WINDOW); let mode = ""; switch (data.gameMode) { - case GameModes.DAILY: - mode = i18next.t("gameMode:dailyRun"); - break; - case GameModes.SPLICED_ENDLESS: - case GameModes.ENDLESS: - mode = i18next.t("gameMode:endless"); - break; - case GameModes.CLASSIC: - mode = i18next.t("gameMode:classic"); - break; - case GameModes.CHALLENGE: - mode = i18next.t("gameMode:challenge"); - break; + case GameModes.DAILY: + mode = i18next.t("gameMode:dailyRun"); + break; + case GameModes.SPLICED_ENDLESS: + case GameModes.ENDLESS: + mode = i18next.t("gameMode:endless"); + break; + case GameModes.CLASSIC: + mode = i18next.t("gameMode:classic"); + break; + case GameModes.CHALLENGE: + mode = i18next.t("gameMode:challenge"); + break; } gameModeLabel.appendText(mode, false); if (data.gameMode === GameModes.SPLICED_ENDLESS) { diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index d5f04f90e5b..0ca47241136 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -5,6 +5,7 @@ import { SessionSaveData } from "../system/game-data"; import { TextStyle, addTextObject, addBBCodeTextObject, getTextColor } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; +import { getPokeballAtlasKey } from "#app/data/pokeball"; import * as Utils from "../utils"; import PokemonData from "../system/pokemon-data"; import i18next from "i18next"; @@ -22,6 +23,8 @@ import * as Modifier from "../modifier/modifier"; import { Species } from "#enums/species"; import { PlayerGender } from "#enums/player-gender"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; +import { getBiomeName } from "#app/data/balance/biomes"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; /** * RunInfoUiMode indicates possible overlays of RunInfoUiHandler. @@ -34,6 +37,11 @@ enum RunInfoUiMode { ENDING_ART } +export enum RunDisplayMode { + RUN_HISTORY, + SESSION_PREVIEW +} + /** * Some variables are protected because this UI class will most likely be extended in the future to display more information. * These variables will most likely be shared across 'classes' aka pages. @@ -41,6 +49,7 @@ enum RunInfoUiMode { * For now, I leave as is. */ export default class RunInfoUiHandler extends UiHandler { + protected runDisplayMode: RunDisplayMode; protected runInfo: SessionSaveData; protected isVictory: boolean; protected pageMode: RunInfoUiMode; @@ -66,6 +75,7 @@ export default class RunInfoUiHandler extends UiHandler { // The import of the modifiersModule is loaded here to sidestep async/await issues. this.modifiersModule = Modifier; this.runContainer.setVisible(false); + this.scene.loadImage("encounter_exclaim", "mystery-encounters"); } /** @@ -87,9 +97,15 @@ export default class RunInfoUiHandler extends UiHandler { this.runContainer.add(gameStatsBg); const run = args[0]; + this.runDisplayMode = args[1]; + if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { + this.runInfo = this.scene.gameData.parseSessionData(JSON.stringify(run.entry)); + this.isVictory = run.isVictory ?? false; + } else if (this.runDisplayMode === RunDisplayMode.SESSION_PREVIEW) { + this.runInfo = args[0]; + } // Assigning information necessary for the UI's creation - this.runInfo = this.scene.gameData.parseSessionData(JSON.stringify(run.entry)); - this.isVictory = run.isVictory; + this.pageMode = RunInfoUiMode.MAIN; // Creates Header and adds to this.runContainer @@ -102,7 +118,11 @@ export default class RunInfoUiHandler extends UiHandler { const runResultWindow = addWindow(this.scene, 0, 0, this.statsBgWidth - 11, 65); runResultWindow.setOrigin(0, 0); this.runResultContainer.add(runResultWindow); - this.parseRunResult(); + if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { + this.parseRunResult(); + } else if (this.runDisplayMode === RunDisplayMode.SESSION_PREVIEW) { + this.parseRunStatus(); + } // Creates Run Info Container this.runInfoContainer = this.scene.add.container(0, 89); @@ -209,14 +229,14 @@ export default class RunInfoUiHandler extends UiHandler { // Wild - Single and Doubles if (this.runInfo.battleType === BattleType.WILD || (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER && !this.runInfo.trainer)) { switch (this.runInfo.enemyParty.length) { - case 1: + case 1: // Wild - Singles - this.parseWildSingleDefeat(enemyContainer); - break; - case 2: + this.parseWildSingleDefeat(enemyContainer); + break; + case 2: //Wild - Doubles - this.parseWildDoubleDefeat(enemyContainer); - break; + this.parseWildDoubleDefeat(enemyContainer); + break; } } else if (this.runInfo.battleType === BattleType.TRAINER || (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER && this.runInfo.trainer)) { this.parseTrainerDefeat(enemyContainer); @@ -226,6 +246,66 @@ export default class RunInfoUiHandler extends UiHandler { this.runContainer.add(this.runResultContainer); } + /** + * This function is used when the Run Info UI is used to preview a Session. + * It edits {@linkcode runResultContainer}, but most importantly - does not display the negative results of a Mystery Encounter or any details of a trainer's party. + * Trainer Parties are replaced with their sprites, names, and their party size. + * Mystery Encounters contain sprites associated with MEs + the title of the specific ME. + */ + private parseRunStatus() { + const runStatusText = addTextObject(this.scene, 6, 5, `${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex} - ${getBiomeName(this.runInfo.arena.biome)}`, TextStyle.WINDOW, { fontSize : "65px", lineSpacing: 0.1 }); + + const enemyContainer = this.scene.add.container(0, 0); + this.runResultContainer.add(enemyContainer); + if (this.runInfo.battleType === BattleType.WILD) { + if (this.runInfo.enemyParty.length === 1) { + this.parseWildSingleDefeat(enemyContainer); + } else if (this.runInfo.enemyParty.length === 2) { + this.parseWildDoubleDefeat(enemyContainer); + } + } else if (this.runInfo.battleType === BattleType.TRAINER) { + this.showTrainerSprites(enemyContainer); + const row_limit = 3; + this.runInfo.enemyParty.forEach((p, i) => { + const pokeball = this.scene.add.sprite(0, 0, "pb"); + pokeball.setFrame(getPokeballAtlasKey(p.pokeball)); + pokeball.setScale(0.5); + pokeball.setPosition(52 + ((i % row_limit) * 8), (i <= 2) ? 18 : 25); + enemyContainer.add(pokeball); + }); + const trainerObj = this.runInfo.trainer.toTrainer(this.scene); + const RIVAL_TRAINER_ID_THRESHOLD = 375; + let trainerName = ""; + if (this.runInfo.trainer.trainerType >= RIVAL_TRAINER_ID_THRESHOLD) { + trainerName = (trainerObj.variant === TrainerVariant.FEMALE) ? i18next.t("trainerNames:rival_female") : i18next.t("trainerNames:rival"); + } else { + trainerName = trainerObj.getName(0, true); + } + const boxString = i18next.t(trainerObj.variant !== TrainerVariant.DOUBLE ? "battle:trainerAppeared" : "battle:trainerAppearedDouble", { trainerName: trainerName }).replace(/\n/g, " "); + const descContainer = this.scene.add.container(0, 0); + const textBox = addTextObject(this.scene, 0, 0, boxString, TextStyle.WINDOW, { fontSize : "35px", wordWrap: { width: 200 }}); + descContainer.add(textBox); + descContainer.setPosition(52, 29); + this.runResultContainer.add(descContainer); + } else if (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER) { + const encounterExclaim = this.scene.add.sprite(0, 0, "encounter_exclaim"); + encounterExclaim.setPosition(34, 26); + encounterExclaim.setScale(0.65); + const subSprite = this.scene.add.sprite(56, -106, "pkmn__sub"); + subSprite.setScale(0.65); + subSprite.setPosition(34, 46); + const mysteryEncounterTitle = i18next.t(this.scene.getMysteryEncounter(this.runInfo.mysteryEncounterType as MysteryEncounterType, true).localizationKey + ":title"); + const descContainer = this.scene.add.container(0, 0); + const textBox = addTextObject(this.scene, 0, 0, mysteryEncounterTitle, TextStyle.WINDOW, { fontSize : "45px", wordWrap: { width: 160 }}); + descContainer.add(textBox); + descContainer.setPosition(47, 37); + this.runResultContainer.add([ encounterExclaim, subSprite, descContainer ]); + } + + this.runResultContainer.add(runStatusText); + this.runContainer.add(this.runResultContainer); + } + /** * This function is called to edit an enemyContainer to represent a loss from a defeat by a wild single Pokemon battle. * @param enemyContainer - container holding enemy visual and level information @@ -278,40 +358,58 @@ export default class RunInfoUiHandler extends UiHandler { } /** - * This edits a container to represent a loss from a defeat by a trainer battle. - * @param enemyContainer - container holding enemy visuals and level information - * The trainers are placed to the left of their party. - * Depending on the trainer icon, there may be overlap between the edges of the box or their party. (Capes...) - * - * Party Pokemon have their icons, terastalization status, and level shown. + * This loads the enemy sprites, positions, and scales them according to the current display mode of the RunInfo UI and then adds them to the container parameter. + * Used by {@linkcode parseRunStatus} and {@linkcode parseTrainerDefeat} + * @param enemyContainer a Phaser Container that should hold enemy sprites */ - private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) { + private showTrainerSprites(enemyContainer: Phaser.GameObjects.Container) { // Creating the trainer sprite and adding it to enemyContainer const tObj = this.runInfo.trainer.toTrainer(this.scene); - // Loads trainer assets on demand, as they are not loaded by default in the scene tObj.config.loadAssets(this.scene, this.runInfo.trainer.variant).then(() => { const tObjSpriteKey = tObj.config.getSpriteKey(this.runInfo.trainer.variant === TrainerVariant.FEMALE, false); const tObjSprite = this.scene.add.sprite(0, 5, tObjSpriteKey); - if (this.runInfo.trainer.variant === TrainerVariant.DOUBLE) { + if (this.runInfo.trainer.variant === TrainerVariant.DOUBLE && !tObj.config.doubleOnly) { const doubleContainer = this.scene.add.container(5, 8); tObjSprite.setPosition(-3, -3); const tObjPartnerSpriteKey = tObj.config.getSpriteKey(true, true); const tObjPartnerSprite = this.scene.add.sprite(5, -3, tObjPartnerSpriteKey); // Double Trainers have smaller sprites than Single Trainers - tObjPartnerSprite.setScale(0.20); - tObjSprite.setScale(0.20); - doubleContainer.add(tObjSprite); - doubleContainer.add(tObjPartnerSprite); - doubleContainer.setPosition(12, 38); + if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { + tObjPartnerSprite.setScale(0.20); + tObjSprite.setScale(0.20); + doubleContainer.add(tObjSprite); + doubleContainer.add(tObjPartnerSprite); + doubleContainer.setPosition(12, 38); + } else { + tObjSprite.setScale(0.55); + tObjSprite.setPosition(-9, -3); + tObjPartnerSprite.setScale(0.55); + doubleContainer.add([ tObjSprite, tObjPartnerSprite ]); + doubleContainer.setPosition(28, 40); + } enemyContainer.add(doubleContainer); } else { - tObjSprite.setScale(0.35, 0.35); - tObjSprite.setPosition(12, 28); + const scale = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? 0.35 : 0.65; + const position = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? [ 12, 28 ] : [ 32, 36 ]; + tObjSprite.setScale(scale, scale); + tObjSprite.setPosition(position[0], position[1]); enemyContainer.add(tObjSprite); } }); + } + /** + * This edits a container to represent a loss from a defeat by a trainer battle. + * The trainers are placed to the left of their party. + * Depending on the trainer icon, there may be overlap between the edges of the box or their party. (Capes...) + * + * Party Pokemon have their icons, terastalization status, and level shown. + * @param enemyContainer - container holding enemy visuals and level information + */ + private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) { + // Loads and adds trainer sprites to the UI + this.showTrainerSprites(enemyContainer); // Determining which Terastallize Modifier belongs to which Pokemon // Creates a dictionary {PokemonId: TeraShardType} const teraPokemon = {}; @@ -376,33 +474,33 @@ export default class RunInfoUiHandler extends UiHandler { modeText.setPosition(7, 5); modeText.appendText(i18next.t("runHistory:mode") + ": ", false); switch (this.runInfo.gameMode) { - case GameModes.DAILY: - modeText.appendText(`${i18next.t("gameMode:dailyRun")}`, false); - break; - case GameModes.SPLICED_ENDLESS: - modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false); - break; - case GameModes.CHALLENGE: - modeText.appendText(`${i18next.t("gameMode:challenge")}`, false); - modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `); - modeText.setWrapMode(1); // wrap by word - modeText.setWrapWidth(500); - const rules: string[] = this.challengeParser(); - if (rules) { - for (let i = 0; i < rules.length; i++) { - if (i > 0) { - modeText.appendText(" + ", false); + case GameModes.DAILY: + modeText.appendText(`${i18next.t("gameMode:dailyRun")}`, false); + break; + case GameModes.SPLICED_ENDLESS: + modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false); + break; + case GameModes.CHALLENGE: + modeText.appendText(`${i18next.t("gameMode:challenge")}`, false); + modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `); + modeText.setWrapMode(1); // wrap by word + modeText.setWrapWidth(500); + const rules: string[] = this.challengeParser(); + if (rules) { + for (let i = 0; i < rules.length; i++) { + if (i > 0) { + modeText.appendText(" + ", false); + } + modeText.appendText(rules[i], false); } - modeText.appendText(rules[i], false); } - } - break; - case GameModes.ENDLESS: - modeText.appendText(`${i18next.t("gameMode:endless")}`, false); - break; - case GameModes.CLASSIC: - modeText.appendText(`${i18next.t("gameMode:classic")}`, false); - break; + break; + case GameModes.ENDLESS: + modeText.appendText(`${i18next.t("gameMode:endless")}`, false); + break; + case GameModes.CLASSIC: + modeText.appendText(`${i18next.t("gameMode:classic")}`, false); + break; } // If the player achieves a personal best in Endless, the mode text will be tinted similarly to SSS luck to celebrate their achievement. @@ -479,23 +577,23 @@ export default class RunInfoUiHandler extends UiHandler { for (let i = 0; i < this.runInfo.challenges.length; i++) { if (this.runInfo.challenges[i].value !== 0) { switch (this.runInfo.challenges[i].id) { - case Challenges.SINGLE_GENERATION: - rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`)); - break; - case Challenges.SINGLE_TYPE: - const typeRule = Type[this.runInfo.challenges[i].value - 1]; - const typeTextColor = `[color=${TypeColor[typeRule]}]`; - const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`; - const typeText = typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]"; - rules.push(typeText); - break; - case Challenges.FRESH_START: - rules.push(i18next.t("challenges:freshStart.name")); - break; - case Challenges.INVERSE_BATTLE: - // - rules.push(i18next.t("challenges:inverseBattle.shortName")); - break; + case Challenges.SINGLE_GENERATION: + rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`)); + break; + case Challenges.SINGLE_TYPE: + const typeRule = Type[this.runInfo.challenges[i].value - 1]; + const typeTextColor = `[color=${TypeColor[typeRule]}]`; + const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`; + const typeText = typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]"; + rules.push(typeText); + break; + case Challenges.INVERSE_BATTLE: + rules.push(i18next.t("challenges:inverseBattle.shortName")); + break; + default: + const localisationKey = Challenges[this.runInfo.challenges[i].id].split("_").map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); + rules.push(i18next.t(`challenges:${localisationKey}.name`)); + break; } } } @@ -813,36 +911,36 @@ export default class RunInfoUiHandler extends UiHandler { const error = false; switch (button) { - case Button.CANCEL: - success = true; - if (this.pageMode === RunInfoUiMode.MAIN) { - this.runInfoContainer.removeAll(true); - this.runResultContainer.removeAll(true); - this.partyContainer.removeAll(true); - this.runContainer.removeAll(true); - if (this.isVictory) { - this.hallofFameContainer.removeAll(true); + case Button.CANCEL: + success = true; + if (this.pageMode === RunInfoUiMode.MAIN) { + this.runInfoContainer.removeAll(true); + this.runResultContainer.removeAll(true); + this.partyContainer.removeAll(true); + this.runContainer.removeAll(true); + if (this.isVictory) { + this.hallofFameContainer.removeAll(true); + } + super.clear(); + this.runContainer.setVisible(false); + ui.revertMode(); + } else if (this.pageMode === RunInfoUiMode.HALL_OF_FAME) { + this.hallofFameContainer.setVisible(false); + this.pageMode = RunInfoUiMode.MAIN; + } else if (this.pageMode === RunInfoUiMode.ENDING_ART) { + this.endCardContainer.setVisible(false); + this.runContainer.remove(this.endCardContainer); + this.pageMode = RunInfoUiMode.MAIN; } - super.clear(); - this.runContainer.setVisible(false); - ui.revertMode(); - } else if (this.pageMode === RunInfoUiMode.HALL_OF_FAME) { - this.hallofFameContainer.setVisible(false); - this.pageMode = RunInfoUiMode.MAIN; - } else if (this.pageMode === RunInfoUiMode.ENDING_ART) { - this.endCardContainer.setVisible(false); - this.runContainer.remove(this.endCardContainer); - this.pageMode = RunInfoUiMode.MAIN; - } - break; - case Button.DOWN: - case Button.UP: - break; - case Button.CYCLE_FORM: - case Button.CYCLE_SHINY: - case Button.CYCLE_ABILITY: - this.buttonCycleOption(button); - break; + break; + case Button.DOWN: + case Button.UP: + break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + case Button.CYCLE_ABILITY: + this.buttonCycleOption(button); + break; } if (success) { @@ -862,40 +960,40 @@ export default class RunInfoUiHandler extends UiHandler { */ private buttonCycleOption(button: Button) { switch (button) { - case Button.CYCLE_FORM: - if (this.isVictory && this.pageMode !== RunInfoUiMode.HALL_OF_FAME) { - if (!this.endCardContainer || !this.endCardContainer.visible) { - this.createVictorySplash(); - this.endCardContainer.setVisible(true); - this.runContainer.add(this.endCardContainer); - this.pageMode = RunInfoUiMode.ENDING_ART; - } else { - this.endCardContainer.setVisible(false); - this.runContainer.remove(this.endCardContainer); - this.pageMode = RunInfoUiMode.MAIN; + case Button.CYCLE_FORM: + if (this.isVictory && this.pageMode !== RunInfoUiMode.HALL_OF_FAME) { + if (!this.endCardContainer || !this.endCardContainer.visible) { + this.createVictorySplash(); + this.endCardContainer.setVisible(true); + this.runContainer.add(this.endCardContainer); + this.pageMode = RunInfoUiMode.ENDING_ART; + } else { + this.endCardContainer.setVisible(false); + this.runContainer.remove(this.endCardContainer); + this.pageMode = RunInfoUiMode.MAIN; + } } - } - break; - case Button.CYCLE_SHINY: - if (this.isVictory && this.pageMode !== RunInfoUiMode.ENDING_ART) { - if (!this.hallofFameContainer.visible) { - this.hallofFameContainer.setVisible(true); - this.pageMode = RunInfoUiMode.HALL_OF_FAME; - } else { - this.hallofFameContainer.setVisible(false); - this.pageMode = RunInfoUiMode.MAIN; + break; + case Button.CYCLE_SHINY: + if (this.isVictory && this.pageMode !== RunInfoUiMode.ENDING_ART) { + if (!this.hallofFameContainer.visible) { + this.hallofFameContainer.setVisible(true); + this.pageMode = RunInfoUiMode.HALL_OF_FAME; + } else { + this.hallofFameContainer.setVisible(false); + this.pageMode = RunInfoUiMode.MAIN; + } } - } - break; - case Button.CYCLE_ABILITY: - if (this.runInfo.modifiers.length !== 0 && this.pageMode === RunInfoUiMode.MAIN) { - if (this.partyVisibility) { - this.showParty(false); - } else { - this.showParty(true); + break; + case Button.CYCLE_ABILITY: + if (this.runInfo.modifiers.length !== 0 && this.pageMode === RunInfoUiMode.MAIN) { + if (this.partyVisibility) { + this.showParty(false); + } else { + this.showParty(true); + } } - } - break; + break; } } } diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 89b20322a68..7bfecb1f99b 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -10,8 +10,10 @@ import MessageUiHandler from "./message-ui-handler"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; +import { RunDisplayMode } from "#app/ui/run-info-ui-handler"; -const sessionSlotCount = 5; +const SESSION_SLOTS_COUNT = 5; +const SLOTS_ON_SCREEN = 3; export enum SaveSlotUiMode { LOAD, @@ -33,7 +35,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { private scrollCursor: integer = 0; - private cursorObj: Phaser.GameObjects.NineSlice | null; + private cursorObj: Phaser.GameObjects.Container | null; private sessionSlotsContainerInitialY: number; @@ -84,9 +86,9 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { this.saveSlotSelectContainer.setVisible(true); this.populateSessionSlots(); + this.setScrollCursor(0); this.setCursor(0); - return true; } @@ -104,40 +106,40 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { error = true; } else { switch (this.uiMode) { - case SaveSlotUiMode.LOAD: - this.saveSlotSelectCallback = null; - originalCallback && originalCallback(cursor); - break; - case SaveSlotUiMode.SAVE: - const saveAndCallback = () => { - const originalCallback = this.saveSlotSelectCallback; + case SaveSlotUiMode.LOAD: this.saveSlotSelectCallback = null; - ui.revertMode(); - ui.showText("", 0); - ui.setMode(Mode.MESSAGE); originalCallback && originalCallback(cursor); - }; - if (this.sessionSlots[cursor].hasData) { - ui.showText(i18next.t("saveSlotSelectUiHandler:overwriteData"), null, () => { - ui.setOverlayMode(Mode.CONFIRM, () => { - this.scene.gameData.deleteSession(cursor).then(response => { - if (response === false) { - this.scene.reset(true); - } else { - saveAndCallback(); - } - }); - }, () => { - ui.revertMode(); - ui.showText("", 0); - }, false, 0, 19, 2000); - }); - } else if (this.sessionSlots[cursor].hasData === false) { - saveAndCallback(); - } else { - return false; - } - break; + break; + case SaveSlotUiMode.SAVE: + const saveAndCallback = () => { + const originalCallback = this.saveSlotSelectCallback; + this.saveSlotSelectCallback = null; + ui.revertMode(); + ui.showText("", 0); + ui.setMode(Mode.MESSAGE); + originalCallback && originalCallback(cursor); + }; + if (this.sessionSlots[cursor].hasData) { + ui.showText(i18next.t("saveSlotSelectUiHandler:overwriteData"), null, () => { + ui.setOverlayMode(Mode.CONFIRM, () => { + this.scene.gameData.deleteSession(cursor).then(response => { + if (response === false) { + this.scene.reset(true); + } else { + saveAndCallback(); + } + }); + }, () => { + ui.revertMode(); + ui.showText("", 0); + }, false, 0, 19, 2000); + }); + } else if (this.sessionSlots[cursor].hasData === false) { + saveAndCallback(); + } else { + return false; + } + break; } success = true; } @@ -147,21 +149,28 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { success = true; } } else { + const cursorPosition = this.cursor + this.scrollCursor; switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1); - } - break; - case Button.DOWN: - if (this.cursor < 2) { - success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < sessionSlotCount - 3) { - success = this.setScrollCursor(this.scrollCursor + 1); - } - break; + case Button.UP: + if (this.cursor) { + // Check to prevent cursor from accessing a negative index + success = (this.cursor === 0) ? this.setCursor(this.cursor) : this.setCursor(this.cursor - 1, cursorPosition); + } else if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1, cursorPosition); + } + break; + case Button.DOWN: + if (this.cursor < (SLOTS_ON_SCREEN - 1)) { + success = this.setCursor(this.cursor + 1, cursorPosition); + } else if (this.scrollCursor < SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN) { + success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition); + } + break; + case Button.RIGHT: + if (this.sessionSlots[cursorPosition].hasData && this.sessionSlots[cursorPosition].saveData) { + this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.sessionSlots[cursorPosition].saveData, RunDisplayMode.SESSION_PREVIEW); + success = true; + } } } @@ -175,12 +184,18 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } populateSessionSlots() { - for (let s = 0; s < sessionSlotCount; s++) { + for (let s = 0; s < SESSION_SLOTS_COUNT; s++) { const sessionSlot = new SessionSlot(this.scene, s); - sessionSlot.load(); this.scene.add.existing(sessionSlot); this.sessionSlotsContainer.add(sessionSlot); this.sessionSlots.push(sessionSlot); + sessionSlot.load().then((success) => { + // If the cursor was moved to this slot while the session was loading + // call setCursor again to shift the slot position and show the arrow for save preview + if (success && (this.cursor + this.scrollCursor) === s) { + this.setCursor(s); + } + }); } } @@ -198,25 +213,79 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { this.saveSlotSelectMessageBoxContainer.setVisible(!!text?.length); } - setCursor(cursor: integer): boolean { + /** + * Move the cursor to a new position and update the view accordingly + * @param cursor the new cursor position, between `0` and `SLOTS_ON_SCREEN - 1` + * @param prevSlotIndex index of the previous session occupied by the cursor, between `0` and `SESSION_SLOTS_COUNT - 1` - optional + * @returns `true` if the cursor position has changed | `false` if it has not + */ + override setCursor(cursor: integer, prevSlotIndex?: integer): boolean { const changed = super.setCursor(cursor); if (!this.cursorObj) { - this.cursorObj = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 44, 6, 6, 6, 6); - this.cursorObj.setOrigin(0, 0); + this.cursorObj = this.scene.add.container(0, 0); + const cursorBox = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 44, 6, 6, 6, 6); + const rightArrow = this.scene.add.image(0, 0, "cursor"); + rightArrow.setPosition(160, 0); + rightArrow.setName("rightArrow"); + this.cursorObj.add([ cursorBox, rightArrow ]); this.sessionSlotsContainer.add(this.cursorObj); } - this.cursorObj.setPosition(4, 4 + (cursor + this.scrollCursor) * 56); + const cursorPosition = cursor + this.scrollCursor; + const cursorIncrement = cursorPosition * 56; + if (this.sessionSlots[cursorPosition] && this.cursorObj) { + const hasData = this.sessionSlots[cursorPosition].hasData; + // If the session slot lacks session data, it does not move from its default, central position. + // Only session slots with session data will move leftwards and have a visible arrow. + if (!hasData) { + this.cursorObj.setPosition(151, 26 + cursorIncrement); + this.sessionSlots[cursorPosition].setPosition(0, cursorIncrement); + } else { + this.cursorObj.setPosition(145, 26 + cursorIncrement); + this.sessionSlots[cursorPosition].setPosition(-6, cursorIncrement); + } + this.setArrowVisibility(hasData); + } + if (!Utils.isNullOrUndefined(prevSlotIndex)) { + this.revertSessionSlot(prevSlotIndex); + } return changed; } - setScrollCursor(scrollCursor: integer): boolean { + /** + * Helper function that resets the given session slot to its default central position + */ + revertSessionSlot(slotIndex: integer): void { + const sessionSlot = this.sessionSlots[slotIndex]; + if (sessionSlot) { + sessionSlot.setPosition(0, slotIndex * 56); + } + } + + /** + * Helper function that checks if the session slot involved holds data or not + * @param hasData `true` if session slot contains data | 'false' if not + */ + setArrowVisibility(hasData: boolean): void { + if (this.cursorObj) { + const rightArrow = this.cursorObj?.getByName("rightArrow") as Phaser.GameObjects.Image; + rightArrow.setVisible(hasData); + } + } + + /** + * Move the scrolling cursor to a new position and update the view accordingly + * @param scrollCursor the new cursor position, between `0` and `SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN` + * @param prevSlotIndex index of the previous slot occupied by the cursor, between `0` and `SESSION_SLOTS_COUNT-1` - optional + * @returns `true` if the cursor position has changed | `false` if it has not + */ + setScrollCursor(scrollCursor: integer, prevSlotIndex?: integer): boolean { const changed = scrollCursor !== this.scrollCursor; if (changed) { this.scrollCursor = scrollCursor; - this.setCursor(this.cursor); + this.setCursor(this.cursor, prevSlotIndex); this.scene.tweens.add({ targets: this.sessionSlotsContainer, y: this.sessionSlotsContainerInitialY - 56 * scrollCursor, @@ -231,6 +300,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { clear() { super.clear(); this.saveSlotSelectContainer.setVisible(false); + this.setScrollCursor(0); this.eraseCursor(); this.saveSlotSelectCallback = null; this.clearSessionSlots(); @@ -254,6 +324,8 @@ class SessionSlot extends Phaser.GameObjects.Container { public hasData: boolean; private loadingLabel: Phaser.GameObjects.Text; + public saveData: SessionSaveData; + constructor(scene: BattleScene, slotId: integer) { super(scene, 0, slotId * 56); @@ -330,6 +402,10 @@ class SessionSlot extends Phaser.GameObjects.Container { load(): Promise { return new Promise(resolve => { this.scene.gameData.getSession(this.slotId).then(async sessionData => { + // Ignore the results if the view was exited + if (!this.active) { + return; + } if (!sessionData) { this.hasData = false; this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty")); @@ -337,6 +413,7 @@ class SessionSlot extends Phaser.GameObjects.Container { return; } this.hasData = true; + this.saveData = sessionData; await this.setupWithData(sessionData); resolve(true); }); diff --git a/src/ui/scrollable-grid-handler.ts b/src/ui/scrollable-grid-handler.ts index f96f3c995b7..cced92a2083 100644 --- a/src/ui/scrollable-grid-handler.ts +++ b/src/ui/scrollable-grid-handler.ts @@ -106,48 +106,48 @@ export default class ScrollableGridUiHandler { const itemOffset = this.scrollCursor * this.COLUMNS; const lastVisibleIndex = Math.min(this.totalElements - 1, this.totalElements - maxScrollCursor * this.COLUMNS - 1); switch (button) { - case Button.UP: - if (currentRowIndex > 0) { - success = this.setCursor(this.cursor - this.COLUMNS); - } else if (this.scrollCursor > 0) { - success = this.setScrollCursor(this.scrollCursor - 1); - } else { + case Button.UP: + if (currentRowIndex > 0) { + success = this.setCursor(this.cursor - this.COLUMNS); + } else if (this.scrollCursor > 0) { + success = this.setScrollCursor(this.scrollCursor - 1); + } else { // wrap around to the last row - let newCursor = this.cursor + (onScreenRows - 1) * this.COLUMNS; - if (newCursor > lastVisibleIndex) { - newCursor -= this.COLUMNS; + let newCursor = this.cursor + (onScreenRows - 1) * this.COLUMNS; + if (newCursor > lastVisibleIndex) { + newCursor -= this.COLUMNS; + } + success = this.setScrollCursor(maxScrollCursor, newCursor); } - success = this.setScrollCursor(maxScrollCursor, newCursor); - } - break; - case Button.DOWN: - if (currentRowIndex < onScreenRows - 1) { + break; + case Button.DOWN: + if (currentRowIndex < onScreenRows - 1) { // Go down one row - success = this.setCursor(Math.min(this.cursor + this.COLUMNS, this.totalElements - itemOffset - 1)); - } else if (this.scrollCursor < maxScrollCursor) { + success = this.setCursor(Math.min(this.cursor + this.COLUMNS, this.totalElements - itemOffset - 1)); + } else if (this.scrollCursor < maxScrollCursor) { // Scroll down one row - success = this.setScrollCursor(this.scrollCursor + 1); - } else { + success = this.setScrollCursor(this.scrollCursor + 1); + } else { // Wrap around to the top row - success = this.setScrollCursor(0, this.cursor % this.COLUMNS); - } - break; - case Button.LEFT: - if (currentColumnIndex > 0) { - success = this.setCursor(this.cursor - 1); - } else if (this.scrollCursor === maxScrollCursor && currentRowIndex === onScreenRows - 1) { - success = this.setCursor(lastVisibleIndex); - } else { - success = this.setCursor(this.cursor + this.COLUMNS - 1); - } - break; - case Button.RIGHT: - if (currentColumnIndex < this.COLUMNS - 1 && this.cursor + itemOffset < this.totalElements - 1) { - success = this.setCursor(this.cursor + 1); - } else { - success = this.setCursor(this.cursor - currentColumnIndex); - } - break; + success = this.setScrollCursor(0, this.cursor % this.COLUMNS); + } + break; + case Button.LEFT: + if (currentColumnIndex > 0) { + success = this.setCursor(this.cursor - 1); + } else if (this.scrollCursor === maxScrollCursor && currentRowIndex === onScreenRows - 1) { + success = this.setCursor(lastVisibleIndex); + } else { + success = this.setCursor(this.cursor + this.COLUMNS - 1); + } + break; + case Button.RIGHT: + if (currentColumnIndex < this.COLUMNS - 1 && this.cursor + itemOffset < this.totalElements - 1) { + success = this.setCursor(this.cursor + 1); + } else { + success = this.setCursor(this.cursor - currentColumnIndex); + } + break; } return success; } diff --git a/src/ui/settings/abstract-binding-ui-handler.ts b/src/ui/settings/abstract-binding-ui-handler.ts index 9ee741f7bd4..9ebc3c493a4 100644 --- a/src/ui/settings/abstract-binding-ui-handler.ts +++ b/src/ui/settings/abstract-binding-ui-handler.ts @@ -170,22 +170,22 @@ export default abstract class AbstractBindingUiHandler extends UiHandler { const ui = this.getUi(); let success = false; switch (button) { - case Button.LEFT: - case Button.RIGHT: + case Button.LEFT: + case Button.RIGHT: // Toggle between action and cancel options. - const cursor = this.cursor ? 0 : 1; - success = this.setCursor(cursor); - break; - case Button.ACTION: + const cursor = this.cursor ? 0 : 1; + success = this.setCursor(cursor); + break; + case Button.ACTION: // Process actions based on current cursor position. - if (this.cursor === 0) { - this.cancelFn && this.cancelFn(); - } else { - success = this.swapAction(); - NavigationManager.getInstance().updateIcons(); - this.cancelFn && this.cancelFn(success); - } - break; + if (this.cursor === 0) { + this.cancelFn && this.cancelFn(); + } else { + success = this.swapAction(); + NavigationManager.getInstance().updateIcons(); + this.cancelFn && this.cancelFn(success); + } + break; } // Plays a select sound effect if an action was successfully processed. diff --git a/src/ui/settings/abstract-control-settings-ui-handler.ts b/src/ui/settings/abstract-control-settings-ui-handler.ts index 477eaf0a68b..69f8eb241d3 100644 --- a/src/ui/settings/abstract-control-settings-ui-handler.ts +++ b/src/ui/settings/abstract-control-settings-ui-handler.ts @@ -449,78 +449,78 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. const setting = this.setting[Object.keys(this.setting)[cursor]]; switch (button) { - case Button.ACTION: - if (!this.optionCursors || !this.optionValueLabels) { - return false; // TODO: is false correct as default? (previously was `undefined`) - } - if (this.settingBlacklisted.includes(setting) || !setting.includes("BUTTON_")) { - success = false; - } else { - success = this.setSetting(this.scene, setting, 1); - } - break; - case Button.UP: // Move up in the menu. - if (!this.optionValueLabels) { - return false; - } - if (cursor) { // If not at the top, move the cursor up. - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else {// If at the top of the visible items, scroll up. - success = this.setScrollCursor(this.scrollCursor - 1); + case Button.ACTION: + if (!this.optionCursors || !this.optionValueLabels) { + return false; // TODO: is false correct as default? (previously was `undefined`) } - } else { + if (this.settingBlacklisted.includes(setting) || !setting.includes("BUTTON_")) { + success = false; + } else { + success = this.setSetting(this.scene, setting, 1); + } + break; + case Button.UP: // Move up in the menu. + if (!this.optionValueLabels) { + return false; + } + if (cursor) { // If not at the top, move the cursor up. + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else {// If at the top of the visible items, scroll up. + success = this.setScrollCursor(this.scrollCursor - 1); + } + } else { // When at the top of the menu and pressing UP, move to the bottommost item. // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(this.rowsToDisplay - 1); - // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); - success = successA && successB; // success is just there to play the little validation sound effect - } - break; - case Button.DOWN: // Move down in the menu. - if (!this.optionValueLabels) { - return false; - } - if (cursor < this.optionValueLabels.length - 1) { - if (this.cursor < this.rowsToDisplay - 1) { - success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { - success = this.setScrollCursor(this.scrollCursor + 1); + const successA = this.setCursor(this.rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect } - } else { + break; + case Button.DOWN: // Move down in the menu. + if (!this.optionValueLabels) { + return false; + } + if (cursor < this.optionValueLabels.length - 1) { + if (this.cursor < this.rowsToDisplay - 1) { + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { + success = this.setScrollCursor(this.scrollCursor + 1); + } + } else { // When at the bottom of the menu and pressing DOWN, move to the topmost item. // First, set the cursor to the first visible element, resetting the scroll to the top. - const successA = this.setCursor(0); - // Then, reset the scroll to start from the first element of the menu. - const successB = this.setScrollCursor(0); - success = successA && successB; // Indicates a successful cursor and scroll adjustment. - } - break; - case Button.LEFT: // Move selection left within the current option set. - if (!this.optionCursors || !this.optionValueLabels) { - return false; // TODO: is false correct as default? (previously was `undefined`) - } - if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { - success = false; - } else if (this.optionCursors[cursor]) { - success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); - } - break; - case Button.RIGHT: // Move selection right within the current option set. - if (!this.optionCursors || !this.optionValueLabels) { - return false; // TODO: is false correct as default? (previously was `undefined`) - } - if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { - success = false; - } else if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { - success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); - } - break; - case Button.CYCLE_FORM: - case Button.CYCLE_SHINY: - success = this.navigationContainer.navigate(button); - break; + const successA = this.setCursor(0); + // Then, reset the scroll to start from the first element of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // Indicates a successful cursor and scroll adjustment. + } + break; + case Button.LEFT: // Move selection left within the current option set. + if (!this.optionCursors || !this.optionValueLabels) { + return false; // TODO: is false correct as default? (previously was `undefined`) + } + if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { + success = false; + } else if (this.optionCursors[cursor]) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); + } + break; + case Button.RIGHT: // Move selection right within the current option set. + if (!this.optionCursors || !this.optionValueLabels) { + return false; // TODO: is false correct as default? (previously was `undefined`) + } + if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { + success = false; + } else if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); + } + break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + success = this.navigationContainer.navigate(button); + break; } } diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 47eceddc813..83219e1ef5a 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -224,59 +224,59 @@ export default class AbstractSettingsUiHandler extends UiHandler { } else { const cursor = this.cursor + this.scrollCursor; switch (button) { - case Button.UP: - if (cursor) { - if (this.cursor) { - success = this.setCursor(this.cursor - 1); + case Button.UP: + if (cursor) { + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else { + success = this.setScrollCursor(this.scrollCursor - 1); + } } else { - success = this.setScrollCursor(this.scrollCursor - 1); - } - } else { // When at the top of the menu and pressing UP, move to the bottommost item. // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(this.rowsToDisplay - 1); - // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); - success = successA && successB; // success is just there to play the little validation sound effect - } - break; - case Button.DOWN: - if (cursor < this.optionValueLabels.length - 1) { - if (this.cursor < this.rowsToDisplay - 1) {// if the visual cursor is in the frame of 0 to 8 - success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { - success = this.setScrollCursor(this.scrollCursor + 1); + const successA = this.setCursor(this.rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect } - } else { + break; + case Button.DOWN: + if (cursor < this.optionValueLabels.length - 1) { + if (this.cursor < this.rowsToDisplay - 1) {// if the visual cursor is in the frame of 0 to 8 + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { + success = this.setScrollCursor(this.scrollCursor + 1); + } + } else { // When at the bottom of the menu and pressing DOWN, move to the topmost item. // First, set the cursor to the first visible element, resetting the scroll to the top. - const successA = this.setCursor(0); - // Then, reset the scroll to start from the first element of the menu. - const successB = this.setScrollCursor(0); - success = successA && successB; // Indicates a successful cursor and scroll adjustment. - } - break; - case Button.LEFT: - if (this.optionCursors[cursor]) {// Moves the option cursor left, if possible. - success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); - } - break; - case Button.RIGHT: + const successA = this.setCursor(0); + // Then, reset the scroll to start from the first element of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // Indicates a successful cursor and scroll adjustment. + } + break; + case Button.LEFT: + if (this.optionCursors[cursor]) {// Moves the option cursor left, if possible. + success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); + } + break; + case Button.RIGHT: // Moves the option cursor right, if possible. - if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { - success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); - } - break; - case Button.CYCLE_FORM: - case Button.CYCLE_SHINY: - success = this.navigationContainer.navigate(button); - break; - case Button.ACTION: - const setting: Setting = this.settings[cursor]; - if (setting?.activatable) { - success = this.activateSetting(setting); - } - break; + if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); + } + break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + success = this.navigationContainer.navigate(button); + break; + case Button.ACTION: + const setting: Setting = this.settings[cursor]; + if (setting?.activatable) { + success = this.activateSetting(setting); + } + break; } } @@ -295,9 +295,9 @@ export default class AbstractSettingsUiHandler extends UiHandler { */ activateSetting(setting: Setting): boolean { switch (setting.key) { - case SettingKeys.Move_Touch_Controls: - this.scene.inputController.moveTouchControlsHandler.enableConfigurationMode(this.getUi(), this.scene); - return true; + case SettingKeys.Move_Touch_Controls: + this.scene.inputController.moveTouchControlsHandler.enableConfigurationMode(this.getUi(), this.scene); + return true; } return false; } diff --git a/src/ui/settings/navigationMenu.ts b/src/ui/settings/navigationMenu.ts index 45209c220fa..ab86fa1569a 100644 --- a/src/ui/settings/navigationMenu.ts +++ b/src/ui/settings/navigationMenu.ts @@ -198,12 +198,12 @@ export default class NavigationMenu extends Phaser.GameObjects.Container { navigate(button: Button): boolean { const navigationManager = NavigationManager.getInstance(); switch (button) { - case Button.CYCLE_FORM: - navigationManager.navigate(this.scene, LEFT); - return true; - case Button.CYCLE_SHINY: - navigationManager.navigate(this.scene, RIGHT); - return true; + case Button.CYCLE_FORM: + navigationManager.navigate(this.scene, LEFT); + return true; + case Button.CYCLE_SHINY: + navigationManager.navigate(this.scene, RIGHT); + return true; } return false; } diff --git a/src/ui/settings/settings-display-ui-handler.ts b/src/ui/settings/settings-display-ui-handler.ts index 3d602c50a78..a25dbf87b7d 100644 --- a/src/ui/settings/settings-display-ui-handler.ts +++ b/src/ui/settings/settings-display-ui-handler.ts @@ -23,79 +23,79 @@ export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler if (languageIndex >= 0) { const currentLocale = localStorage.getItem("prLang"); switch (currentLocale) { - case "en": - this.settings[languageIndex].options[0] = { - value: "English", - label: "English", - }; - break; - case "es": - this.settings[languageIndex].options[0] = { - value: "Español", - label: "Español", - }; - break; - case "it": - this.settings[languageIndex].options[0] = { - value: "Italiano", - label: "Italiano", - }; - break; - case "fr": - this.settings[languageIndex].options[0] = { - value: "Français", - label: "Français", - }; - break; - case "de": - this.settings[languageIndex].options[0] = { - value: "Deutsch", - label: "Deutsch", - }; - break; - case "pt-BR": - this.settings[languageIndex].options[0] = { - value: "Português (BR)", - label: "Português (BR)", - }; - break; - case "zh-CN": - this.settings[languageIndex].options[0] = { - value: "简体中文", - label: "简体中文", - }; - break; - case "zh-TW": - this.settings[languageIndex].options[0] = { - value: "繁體中文", - label: "繁體中文", - }; - break; - case "ko": - case "ko-KR": - this.settings[languageIndex].options[0] = { - value: "한국어", - label: "한국어", - }; - break; - case "ja": - this.settings[languageIndex].options[0] = { - value: "日本語", - label: "日本語", - }; - break; - case "ca-ES": - this.settings[languageIndex].options[0] = { - value: "Català", - label: "Català", - }; - break; - default: - this.settings[languageIndex].options[0] = { - value: "English", - label: "English", - }; - break; + case "en": + this.settings[languageIndex].options[0] = { + value: "English", + label: "English", + }; + break; + case "es": + this.settings[languageIndex].options[0] = { + value: "Español", + label: "Español", + }; + break; + case "it": + this.settings[languageIndex].options[0] = { + value: "Italiano", + label: "Italiano", + }; + break; + case "fr": + this.settings[languageIndex].options[0] = { + value: "Français", + label: "Français", + }; + break; + case "de": + this.settings[languageIndex].options[0] = { + value: "Deutsch", + label: "Deutsch", + }; + break; + case "pt-BR": + this.settings[languageIndex].options[0] = { + value: "Português (BR)", + label: "Português (BR)", + }; + break; + case "zh-CN": + this.settings[languageIndex].options[0] = { + value: "简体中文", + label: "简体中文", + }; + break; + case "zh-TW": + this.settings[languageIndex].options[0] = { + value: "繁體中文", + label: "繁體中文", + }; + break; + case "ko": + case "ko-KR": + this.settings[languageIndex].options[0] = { + value: "한국어", + label: "한국어", + }; + break; + case "ja": + this.settings[languageIndex].options[0] = { + value: "日本語", + label: "日本語", + }; + break; + case "ca-ES": + this.settings[languageIndex].options[0] = { + value: "Català", + label: "Català", + }; + break; + default: + this.settings[languageIndex].options[0] = { + value: "English", + label: "English", + }; + break; } } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 98a563301e4..bb999dc736a 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -46,6 +46,7 @@ import { StarterContainer } from "#app/ui/starter-container"; import { DropDownColumn, FilterBar } from "#app/ui/filter-bar"; import { ScrollBar } from "#app/ui/scroll-bar"; import { SelectChallengePhase } from "#app/phases/select-challenge-phase"; +import { EncounterPhase } from "#app/phases/encounter-phase"; import { TitlePhase } from "#app/phases/title-phase"; import { Abilities } from "#enums/abilities"; import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters"; @@ -1302,119 +1303,119 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } else if (this.startCursorObj.visible) { // this checks to see if the start button is selected switch (button) { - case Button.ACTION: - if (this.tryStart(true)) { - success = true; - } else { - error = true; - } - break; - case Button.UP: + case Button.ACTION: + if (this.tryStart(true)) { + success = true; + } else { + error = true; + } + break; + case Button.UP: // UP from start button: go to pokemon in team if any, otherwise filter - this.startCursorObj.setVisible(false); - if (this.starterSpecies.length > 0) { - this.starterIconsCursorIndex = this.starterSpecies.length - 1; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } else { + this.startCursorObj.setVisible(false); + if (this.starterSpecies.length > 0) { + this.starterIconsCursorIndex = this.starterSpecies.length - 1; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + } else { + this.startCursorObj.setVisible(false); + this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); + this.setFilterMode(true); + } + success = true; + break; + case Button.DOWN: + // DOWN from start button: Go to filters this.startCursorObj.setVisible(false); this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); this.setFilterMode(true); - } - success = true; - break; - case Button.DOWN: - // DOWN from start button: Go to filters - this.startCursorObj.setVisible(false); - this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); - this.setFilterMode(true); - success = true; - break; - case Button.LEFT: - if (numberOfStarters > 0) { - this.startCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9 + 8); // set last column success = true; - } - break; - case Button.RIGHT: - if (numberOfStarters > 0) { - this.startCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9); // set first column - success = true; - } - break; + break; + case Button.LEFT: + if (numberOfStarters > 0) { + this.startCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9 + 8); // set last column + success = true; + } + break; + case Button.RIGHT: + if (numberOfStarters > 0) { + this.startCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9); // set first column + success = true; + } + break; } } else if (this.filterMode) { switch (button) { - case Button.LEFT: - if (this.filterBarCursor > 0) { - success = this.setCursor(this.filterBarCursor - 1); - } else { - success = this.setCursor(this.filterBar.numFilters - 1); - } - break; - case Button.RIGHT: - if (this.filterBarCursor < this.filterBar.numFilters - 1) { - success = this.setCursor(this.filterBarCursor + 1); - } else { - success = this.setCursor(0); - } - break; - case Button.UP: - if (this.filterBar.openDropDown) { - success = this.filterBar.decDropDownCursor(); - } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { - // UP from the last filter, move to start button - this.setFilterMode(false); - this.cursorObj.setVisible(false); - this.startCursorObj.setVisible(true); - success = true; - } else if (numberOfStarters > 0) { - // UP from filter bar to bottom of Pokemon list - this.setFilterMode(false); - this.scrollCursor = Math.max(0, numOfRows - 9); - this.updateScroll(); - const proportion = (this.filterBarCursor + 0.5) / this.filterBar.numFilters; - const targetCol = Math.min(8, Math.floor(proportion * 11)); - if (numberOfStarters % 9 > targetCol) { - this.setCursor(numberOfStarters - (numberOfStarters) % 9 + targetCol); + case Button.LEFT: + if (this.filterBarCursor > 0) { + success = this.setCursor(this.filterBarCursor - 1); } else { - this.setCursor(Math.max(numberOfStarters - (numberOfStarters) % 9 + targetCol - 9, 0)); + success = this.setCursor(this.filterBar.numFilters - 1); + } + break; + case Button.RIGHT: + if (this.filterBarCursor < this.filterBar.numFilters - 1) { + success = this.setCursor(this.filterBarCursor + 1); + } else { + success = this.setCursor(0); + } + break; + case Button.UP: + if (this.filterBar.openDropDown) { + success = this.filterBar.decDropDownCursor(); + } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { + // UP from the last filter, move to start button + this.setFilterMode(false); + this.cursorObj.setVisible(false); + this.startCursorObj.setVisible(true); + success = true; + } else if (numberOfStarters > 0) { + // UP from filter bar to bottom of Pokemon list + this.setFilterMode(false); + this.scrollCursor = Math.max(0, numOfRows - 9); + this.updateScroll(); + const proportion = (this.filterBarCursor + 0.5) / this.filterBar.numFilters; + const targetCol = Math.min(8, Math.floor(proportion * 11)); + if (numberOfStarters % 9 > targetCol) { + this.setCursor(numberOfStarters - (numberOfStarters) % 9 + targetCol); + } else { + this.setCursor(Math.max(numberOfStarters - (numberOfStarters) % 9 + targetCol - 9, 0)); + } + success = true; + } + break; + case Button.DOWN: + if (this.filterBar.openDropDown) { + success = this.filterBar.incDropDownCursor(); + } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { + // DOWN from the last filter, move to Pokemon in party if any + this.setFilterMode(false); + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = 0; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + success = true; + } else if (numberOfStarters > 0) { + // DOWN from filter bar to top of Pokemon list + this.setFilterMode(false); + this.scrollCursor = 0; + this.updateScroll(); + const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1); + const targetCol = Math.min(8, Math.floor(proportion * 11)); + this.setCursor(Math.min(targetCol, numberOfStarters)); + success = true; + } + break; + case Button.ACTION: + if (!this.filterBar.openDropDown) { + this.filterBar.toggleDropDown(this.filterBarCursor); + } else { + this.filterBar.toggleOptionState(); } success = true; - } - break; - case Button.DOWN: - if (this.filterBar.openDropDown) { - success = this.filterBar.incDropDownCursor(); - } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { - // DOWN from the last filter, move to Pokemon in party if any - this.setFilterMode(false); - this.cursorObj.setVisible(false); - this.starterIconsCursorIndex = 0; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - success = true; - } else if (numberOfStarters > 0) { - // DOWN from filter bar to top of Pokemon list - this.setFilterMode(false); - this.scrollCursor = 0; - this.updateScroll(); - const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1); - const targetCol = Math.min(8, Math.floor(proportion * 11)); - this.setCursor(Math.min(targetCol, numberOfStarters)); - success = true; - } - break; - case Button.ACTION: - if (!this.filterBar.openDropDown) { - this.filterBar.toggleDropDown(this.filterBarCursor); - } else { - this.filterBar.toggleOptionState(); - } - success = true; - break; + break; } } else { @@ -1788,7 +1789,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { options.push({ label: `x${sameSpeciesEggCost} ${i18next.t("starterSelectUiHandler:sameSpeciesEgg")}`, handler: () => { - if (this.scene.gameData.eggs.length < 99 && (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost)) { + if ((this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) + && (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost)) { if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) { starterData.candyCount -= sameSpeciesEggCost; } @@ -1854,259 +1856,259 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } else { const props = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); switch (button) { - case Button.CYCLE_SHINY: - if (this.canCycleShiny) { - starterAttributes.shiny = starterAttributes.shiny !== undefined ? !starterAttributes.shiny : false; + case Button.CYCLE_SHINY: + if (this.canCycleShiny) { + starterAttributes.shiny = starterAttributes.shiny !== undefined ? !starterAttributes.shiny : false; - if (starterAttributes.shiny) { + if (starterAttributes.shiny) { // Change to shiny, we need to get the proper default variant - const newProps = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); - const newVariant = starterAttributes.variant ? starterAttributes.variant as Variant : newProps.variant; - this.setSpeciesDetails(this.lastSpecies, true, undefined, undefined, newVariant, undefined, undefined); + const newProps = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); + const newVariant = starterAttributes.variant ? starterAttributes.variant as Variant : newProps.variant; + this.setSpeciesDetails(this.lastSpecies, true, undefined, undefined, newVariant, undefined, undefined); - this.scene.playSound("se/sparkle"); - // Set the variant label to the shiny tint - const tint = getVariantTint(newVariant); - this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant)); - this.pokemonShinyIcon.setTint(tint); - this.pokemonShinyIcon.setVisible(true); - } else { - this.setSpeciesDetails(this.lastSpecies, false, undefined, undefined, 0, undefined, undefined); - this.pokemonShinyIcon.setVisible(false); - success = true; - } - } - break; - case Button.CYCLE_FORM: - if (this.canCycleForm) { - const formCount = this.lastSpecies.forms.length; - let newFormIndex = props.formIndex; - do { - newFormIndex = (newFormIndex + 1) % formCount; - if (this.lastSpecies.forms[newFormIndex].isStarterSelectable && this.speciesStarterDexEntry!.caughtAttr! & this.scene.gameData.getFormAttr(newFormIndex)) { // TODO: are those bangs correct? - break; + this.scene.playSound("se/sparkle"); + // Set the variant label to the shiny tint + const tint = getVariantTint(newVariant); + this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant)); + this.pokemonShinyIcon.setTint(tint); + this.pokemonShinyIcon.setVisible(true); + } else { + this.setSpeciesDetails(this.lastSpecies, false, undefined, undefined, 0, undefined, undefined); + this.pokemonShinyIcon.setVisible(false); + success = true; } - } while (newFormIndex !== props.formIndex); - starterAttributes.form = newFormIndex; // store the selected form - this.setSpeciesDetails(this.lastSpecies, undefined, newFormIndex, undefined, undefined, undefined, undefined); - success = true; - } - break; - case Button.CYCLE_GENDER: - if (this.canCycleGender) { - starterAttributes.female = !props.female; - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, !props.female, undefined, undefined, undefined); - success = true; - } - break; - case Button.CYCLE_ABILITY: - if (this.canCycleAbility) { - const abilityCount = this.lastSpecies.getAbilityCount(); - const abilityAttr = this.scene.gameData.starterData[this.lastSpecies.speciesId].abilityAttr; - const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1; - let newAbilityIndex = this.abilityCursor; - do { - newAbilityIndex = (newAbilityIndex + 1) % abilityCount; - if (newAbilityIndex === 0) { - if (hasAbility1) { + } + break; + case Button.CYCLE_FORM: + if (this.canCycleForm) { + const formCount = this.lastSpecies.forms.length; + let newFormIndex = props.formIndex; + do { + newFormIndex = (newFormIndex + 1) % formCount; + if (this.lastSpecies.forms[newFormIndex].isStarterSelectable && this.speciesStarterDexEntry!.caughtAttr! & this.scene.gameData.getFormAttr(newFormIndex)) { // TODO: are those bangs correct? break; } - } else if (newAbilityIndex === 1) { + } while (newFormIndex !== props.formIndex); + starterAttributes.form = newFormIndex; // store the selected form + this.setSpeciesDetails(this.lastSpecies, undefined, newFormIndex, undefined, undefined, undefined, undefined); + success = true; + } + break; + case Button.CYCLE_GENDER: + if (this.canCycleGender) { + starterAttributes.female = !props.female; + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, !props.female, undefined, undefined, undefined); + success = true; + } + break; + case Button.CYCLE_ABILITY: + if (this.canCycleAbility) { + const abilityCount = this.lastSpecies.getAbilityCount(); + const abilityAttr = this.scene.gameData.starterData[this.lastSpecies.speciesId].abilityAttr; + const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1; + let newAbilityIndex = this.abilityCursor; + do { + newAbilityIndex = (newAbilityIndex + 1) % abilityCount; + if (newAbilityIndex === 0) { + if (hasAbility1) { + break; + } + } else if (newAbilityIndex === 1) { // If ability 1 and 2 are the same and ability 1 is unlocked, skip over ability 2 - if (this.lastSpecies.ability1 === this.lastSpecies.ability2 && hasAbility1) { - newAbilityIndex = (newAbilityIndex + 1) % abilityCount; - } - break; - } else { - if (abilityAttr & AbilityAttr.ABILITY_HIDDEN) { + if (this.lastSpecies.ability1 === this.lastSpecies.ability2 && hasAbility1) { + newAbilityIndex = (newAbilityIndex + 1) % abilityCount; + } break; + } else { + if (abilityAttr & AbilityAttr.ABILITY_HIDDEN) { + break; + } } - } - } while (newAbilityIndex !== this.abilityCursor); - starterAttributes.ability = newAbilityIndex; // store the selected ability + } while (newAbilityIndex !== this.abilityCursor); + starterAttributes.ability = newAbilityIndex; // store the selected ability - const { visible: tooltipVisible } = this.scene.ui.getTooltip(); + const { visible: tooltipVisible } = this.scene.ui.getTooltip(); - if (tooltipVisible && this.activeTooltip === "ABILITY") { - const newAbility = allAbilities[this.lastSpecies.getAbility(newAbilityIndex)]; - this.scene.ui.editTooltip(`${newAbility.name}`, `${newAbility.description}`); - } + if (tooltipVisible && this.activeTooltip === "ABILITY") { + const newAbility = allAbilities[this.lastSpecies.getAbility(newAbilityIndex)]; + this.scene.ui.editTooltip(`${newAbility.name}`, `${newAbility.description}`); + } - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, newAbilityIndex, undefined); - success = true; - } - break; - case Button.CYCLE_NATURE: - if (this.canCycleNature) { - const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); - const natureIndex = natures.indexOf(this.natureCursor); - const newNature = natures[natureIndex < natures.length - 1 ? natureIndex + 1 : 0]; - // store cycled nature as default - starterAttributes.nature = newNature as unknown as integer; - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, newNature, undefined); - success = true; - } - break; - case Button.V: - if (this.canCycleVariant) { - let newVariant = props.variant; - do { - newVariant = (newVariant + 1) % 3; - if (!newVariant) { - if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.DEFAULT_VARIANT) { // TODO: is this bang correct? - break; - } - } else if (newVariant === 1) { - if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_2) { // TODO: is this bang correct? - break; - } - } else { - if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_3) { // TODO: is this bang correct? - break; - } - } - } while (newVariant !== props.variant); - starterAttributes.variant = newVariant; // store the selected variant - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newVariant as Variant, undefined, undefined); - // Cycle tint based on current sprite tint - const tint = getVariantTint(newVariant as Variant); - this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant as Variant)); - this.pokemonShinyIcon.setTint(tint); - success = true; - } - break; - case Button.UP: - if (!this.starterIconsCursorObj.visible) { - if (currentRow > 0) { - if (this.scrollCursor > 0 && currentRow - this.scrollCursor === 0) { - this.scrollCursor--; - this.updateScroll(); - } - success = this.setCursor(this.cursor - 9); - } else { - this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); - this.setFilterMode(true); + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, newAbilityIndex, undefined); success = true; } - } else { - if (this.starterIconsCursorIndex === 0) { + break; + case Button.CYCLE_NATURE: + if (this.canCycleNature) { + const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); + const natureIndex = natures.indexOf(this.natureCursor); + const newNature = natures[natureIndex < natures.length - 1 ? natureIndex + 1 : 0]; + // store cycled nature as default + starterAttributes.nature = newNature as unknown as integer; + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, newNature, undefined); + success = true; + } + break; + case Button.V: + if (this.canCycleVariant) { + let newVariant = props.variant; + do { + newVariant = (newVariant + 1) % 3; + if (!newVariant) { + if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.DEFAULT_VARIANT) { // TODO: is this bang correct? + break; + } + } else if (newVariant === 1) { + if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_2) { // TODO: is this bang correct? + break; + } + } else { + if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_3) { // TODO: is this bang correct? + break; + } + } + } while (newVariant !== props.variant); + starterAttributes.variant = newVariant; // store the selected variant + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newVariant as Variant, undefined, undefined); + // Cycle tint based on current sprite tint + const tint = getVariantTint(newVariant as Variant); + this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant as Variant)); + this.pokemonShinyIcon.setTint(tint); + success = true; + } + break; + case Button.UP: + if (!this.starterIconsCursorObj.visible) { + if (currentRow > 0) { + if (this.scrollCursor > 0 && currentRow - this.scrollCursor === 0) { + this.scrollCursor--; + this.updateScroll(); + } + success = this.setCursor(this.cursor - 9); + } else { + this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); + this.setFilterMode(true); + success = true; + } + } else { + if (this.starterIconsCursorIndex === 0) { // Up from first Pokemon in the team > go to filter - this.starterIconsCursorObj.setVisible(false); - this.setSpecies(null); - this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); - this.setFilterMode(true); - } else { - this.starterIconsCursorIndex--; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } - success = true; - } - break; - case Button.DOWN: - if (!this.starterIconsCursorObj.visible) { - if (currentRow < numOfRows - 1) { // not last row - if (currentRow - this.scrollCursor === 8) { // last row of visible starters - this.scrollCursor++; + this.starterIconsCursorObj.setVisible(false); + this.setSpecies(null); + this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); + this.setFilterMode(true); + } else { + this.starterIconsCursorIndex--; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); } - success = this.setCursor(this.cursor + 9); - this.updateScroll(); - } else if (numOfRows > 1) { - // DOWN from last row of Pokemon > Wrap around to first row - this.scrollCursor = 0; - this.updateScroll(); - success = this.setCursor(this.cursor % 9); - } else { - // DOWN from single row of Pokemon > Go to filters - this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); - this.setFilterMode(true); success = true; } - } else { - if (this.starterIconsCursorIndex <= this.starterSpecies.length - 2) { - this.starterIconsCursorIndex++; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); + break; + case Button.DOWN: + if (!this.starterIconsCursorObj.visible) { + if (currentRow < numOfRows - 1) { // not last row + if (currentRow - this.scrollCursor === 8) { // last row of visible starters + this.scrollCursor++; + } + success = this.setCursor(this.cursor + 9); + this.updateScroll(); + } else if (numOfRows > 1) { + // DOWN from last row of Pokemon > Wrap around to first row + this.scrollCursor = 0; + this.updateScroll(); + success = this.setCursor(this.cursor % 9); + } else { + // DOWN from single row of Pokemon > Go to filters + this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); + this.setFilterMode(true); + success = true; + } } else { - this.starterIconsCursorObj.setVisible(false); - this.setSpecies(null); - this.startCursorObj.setVisible(true); + if (this.starterIconsCursorIndex <= this.starterSpecies.length - 2) { + this.starterIconsCursorIndex++; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + } else { + this.starterIconsCursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); + } + success = true; } - success = true; - } - break; - case Button.LEFT: - if (!this.starterIconsCursorObj.visible) { - if (this.cursor % 9 !== 0) { - success = this.setCursor(this.cursor - 1); - } else { + break; + case Button.LEFT: + if (!this.starterIconsCursorObj.visible) { + if (this.cursor % 9 !== 0) { + success = this.setCursor(this.cursor - 1); + } else { // LEFT from filtered Pokemon, on the left edge - if (this.starterSpecies.length === 0) { + if (this.starterSpecies.length === 0) { // no starter in team > wrap around to the last column - success = this.setCursor(this.cursor + Math.min(8, numberOfStarters - this.cursor)); + success = this.setCursor(this.cursor + Math.min(8, numberOfStarters - this.cursor)); - } else if (onScreenCurrentRow < 7) { + } else if (onScreenCurrentRow < 7) { // at least one pokemon in team > for the first 7 rows, go to closest starter - this.cursorObj.setVisible(false); - this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); - this.moveStarterIconsCursor(this.starterIconsCursorIndex); + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); + this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } else { + } else { // at least one pokemon in team > from the bottom 2 rows, go to start run button - this.cursorObj.setVisible(false); - this.setSpecies(null); - this.startCursorObj.setVisible(true); + this.cursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); + } + success = true; } - success = true; - } - } else if (numberOfStarters > 0) { + } else if (numberOfStarters > 0) { // LEFT from team > Go to closest filtered Pokemon - const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); - this.starterIconsCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9 + 8, onScreenLastIndex)); - success = true; - } else { - // LEFT from team and no Pokemon in filter > do nothing - success = false; - } - break; - case Button.RIGHT: - if (!this.starterIconsCursorObj.visible) { - // is not right edge - if (this.cursor % 9 < (currentRow < numOfRows - 1 ? 8 : (numberOfStarters - 1) % 9)) { - success = this.setCursor(this.cursor + 1); - } else { - // RIGHT from filtered Pokemon, on the right edge - if (this.starterSpecies.length === 0) { - // no selected starter in team > wrap around to the first column - success = this.setCursor(this.cursor - Math.min(8, this.cursor % 9)); - - } else if (onScreenCurrentRow < 7) { - // at least one pokemon in team > for the first 7 rows, go to closest starter - this.cursorObj.setVisible(false); - this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - - } else { - // at least one pokemon in team > from the bottom 2 rows, go to start run button - this.cursorObj.setVisible(false); - this.setSpecies(null); - this.startCursorObj.setVisible(true); - } + const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); + this.starterIconsCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9 + 8, onScreenLastIndex)); success = true; + } else { + // LEFT from team and no Pokemon in filter > do nothing + success = false; } - } else if (numberOfStarters > 0) { + break; + case Button.RIGHT: + if (!this.starterIconsCursorObj.visible) { + // is not right edge + if (this.cursor % 9 < (currentRow < numOfRows - 1 ? 8 : (numberOfStarters - 1) % 9)) { + success = this.setCursor(this.cursor + 1); + } else { + // RIGHT from filtered Pokemon, on the right edge + if (this.starterSpecies.length === 0) { + // no selected starter in team > wrap around to the first column + success = this.setCursor(this.cursor - Math.min(8, this.cursor % 9)); + + } else if (onScreenCurrentRow < 7) { + // at least one pokemon in team > for the first 7 rows, go to closest starter + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + + } else { + // at least one pokemon in team > from the bottom 2 rows, go to start run button + this.cursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); + } + success = true; + } + } else if (numberOfStarters > 0) { // RIGHT from team > Go to closest filtered Pokemon - const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); - this.starterIconsCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9, onScreenLastIndex - (onScreenLastIndex % 9))); - success = true; - } else { + const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); + this.starterIconsCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9, onScreenLastIndex - (onScreenLastIndex % 9))); + success = true; + } else { // RIGHT from team and no Pokemon in filter > do nothing - success = false; - } - break; + success = false; + } + break; } } } @@ -2208,29 +2210,29 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (gamepadType === "touch") { gamepadType = "keyboard"; switch (iconSetting) { - case SettingKeyboard.Button_Cycle_Shiny: - iconPath = "R.png"; - break; - case SettingKeyboard.Button_Cycle_Form: - iconPath = "F.png"; - break; - case SettingKeyboard.Button_Cycle_Gender: - iconPath = "G.png"; - break; - case SettingKeyboard.Button_Cycle_Ability: - iconPath = "E.png"; - break; - case SettingKeyboard.Button_Cycle_Nature: - iconPath = "N.png"; - break; - case SettingKeyboard.Button_Cycle_Variant: - iconPath = "V.png"; - break; - case SettingKeyboard.Button_Stats: - iconPath = "C.png"; - break; - default: - break; + case SettingKeyboard.Button_Cycle_Shiny: + iconPath = "R.png"; + break; + case SettingKeyboard.Button_Cycle_Form: + iconPath = "F.png"; + break; + case SettingKeyboard.Button_Cycle_Gender: + iconPath = "G.png"; + break; + case SettingKeyboard.Button_Cycle_Ability: + iconPath = "E.png"; + break; + case SettingKeyboard.Button_Cycle_Nature: + iconPath = "N.png"; + break; + case SettingKeyboard.Button_Cycle_Variant: + iconPath = "V.png"; + break; + case SettingKeyboard.Button_Stats: + iconPath = "C.png"; + break; + default: + break; } } else { iconPath = this.scene.inputController?.getIconForLatestInputRecorded(iconSetting); @@ -2321,12 +2323,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { getValueLimit(): integer { const valueLimit = new Utils.IntegerHolder(0); switch (this.scene.gameMode.modeId) { - case GameModes.ENDLESS: - case GameModes.SPLICED_ENDLESS: - valueLimit.value = 15; - break; - default: - valueLimit.value = 10; + case GameModes.ENDLESS: + case GameModes.SPLICED_ENDLESS: + valueLimit.value = 15; + break; + default: + valueLimit.value = 10; } Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_POINTS, valueLimit); @@ -2530,22 +2532,22 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const sort = this.filterBar.getVals(DropDownColumn.SORT)[0]; this.filteredStarterContainers.sort((a, b) => { switch (sort.val) { - default: - break; - case SortCriteria.NUMBER: - return (a.species.speciesId - b.species.speciesId) * -sort.dir; - case SortCriteria.COST: - return (a.cost - b.cost) * -sort.dir; - case SortCriteria.CANDY: - const candyCountA = this.scene.gameData.starterData[a.species.speciesId].candyCount; - const candyCountB = this.scene.gameData.starterData[b.species.speciesId].candyCount; - return (candyCountA - candyCountB) * -sort.dir; - case SortCriteria.IV: - const avgIVsA = this.scene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[a.species.speciesId].ivs.length; - const avgIVsB = this.scene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[b.species.speciesId].ivs.length; - return (avgIVsA - avgIVsB) * -sort.dir; - case SortCriteria.NAME: - return a.species.name.localeCompare(b.species.name) * -sort.dir; + default: + break; + case SortCriteria.NUMBER: + return (a.species.speciesId - b.species.speciesId) * -sort.dir; + case SortCriteria.COST: + return (a.cost - b.cost) * -sort.dir; + case SortCriteria.CANDY: + const candyCountA = this.scene.gameData.starterData[a.species.speciesId].candyCount; + const candyCountB = this.scene.gameData.starterData[b.species.speciesId].candyCount; + return (candyCountA - candyCountB) * -sort.dir; + case SortCriteria.IV: + const avgIVsA = this.scene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[a.species.speciesId].ivs.length; + const avgIVsB = this.scene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[b.species.speciesId].ivs.length; + return (avgIVsA - avgIVsB) * -sort.dir; + case SortCriteria.NAME: + return a.species.name.localeCompare(b.species.name) * -sort.dir; } return 0; }); @@ -2961,8 +2963,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.natureCursor = -1; if (this.activeTooltip === "CANDY") { - const { currentFriendship, friendshipCap } = this.getFriendship(this.lastSpecies.speciesId); - this.scene.ui.editTooltip("", `${currentFriendship}/${friendshipCap}`); + if (this.lastSpecies) { + const { currentFriendship, friendshipCap } = this.getFriendship(this.lastSpecies.speciesId); + this.scene.ui.editTooltip("", `${currentFriendship}/${friendshipCap}`); + } else { + this.scene.ui.hideTooltip(); + } } if (species?.forms?.find(f => f.formKey === "female")) { @@ -3356,16 +3362,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { starter.label.setText(valueStr); let textStyle: TextStyle; switch (baseStarterValue - starterValue) { - case 0: - textStyle = TextStyle.WINDOW; - break; - case 1: - case 0.5: - textStyle = TextStyle.SUMMARY_BLUE; - break; - default: - textStyle = TextStyle.SUMMARY_GOLD; - break; + case 0: + textStyle = TextStyle.WINDOW; + break; + case 1: + case 0.5: + textStyle = TextStyle.SUMMARY_BLUE; + break; + default: + textStyle = TextStyle.SUMMARY_GOLD; + break; } if (baseStarterValue - starterValue > 0) { starter.label.setColor(this.getTextColor(textStyle)); @@ -3468,6 +3474,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.scene.clearPhaseQueue(); if (this.scene.gameMode.isChallenge) { this.scene.pushPhase(new SelectChallengePhase(this.scene)); + this.scene.pushPhase(new EncounterPhase(this.scene, false)); } else { this.scene.pushPhase(new TitlePhase(this.scene)); } @@ -3652,6 +3659,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { StarterPrefs.save(this.starterPreferences); this.cursor = -1; this.hideInstructions(); + this.activeTooltip = undefined; + this.scene.ui.hideTooltip(); + this.starterSelectContainer.setVisible(false); this.blockInput = false; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 86b00f512a9..b35c0ca3303 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -406,22 +406,22 @@ export default class SummaryUiHandler extends UiHandler { this.genderText.setShadowColor(getGenderColor(this.pokemon.getGender(true), true)); switch (this.summaryUiMode) { - case SummaryUiMode.DEFAULT: - const page = args.length < 2 ? Page.PROFILE : args[2] as Page; - this.hideMoveEffect(true); - this.setCursor(page); - if (args.length > 3) { - this.selectCallback = args[3]; - } - break; - case SummaryUiMode.LEARN_MOVE: - this.newMove = args[2] as Move; - this.moveSelectFunction = args[3] as Function; + case SummaryUiMode.DEFAULT: + const page = args.length < 2 ? Page.PROFILE : args[2] as Page; + this.hideMoveEffect(true); + this.setCursor(page); + if (args.length > 3) { + this.selectCallback = args[3]; + } + break; + case SummaryUiMode.LEARN_MOVE: + this.newMove = args[2] as Move; + this.moveSelectFunction = args[3] as Function; - this.showMoveEffect(true); - this.setCursor(Page.MOVES); - this.showMoveSelect(); - break; + this.showMoveEffect(true); + this.setCursor(Page.MOVES); + this.showMoveSelect(); + break; } const fromSummary = args.length >= 2; @@ -489,25 +489,25 @@ export default class SummaryUiHandler extends UiHandler { success = true; } else { switch (button) { - case Button.UP: - success = this.setCursor(this.moveCursor ? this.moveCursor - 1 : 4); - break; - case Button.DOWN: - success = this.setCursor(this.moveCursor < 4 ? this.moveCursor + 1 : 0); - break; - case Button.LEFT: - this.moveSelect = false; - this.setCursor(Page.STATS); - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { - this.hideMoveEffect(); - this.destroyBlinkCursor(); - success = true; + case Button.UP: + success = this.setCursor(this.moveCursor ? this.moveCursor - 1 : 4); break; - } else { - this.hideMoveSelect(); - success = true; + case Button.DOWN: + success = this.setCursor(this.moveCursor < 4 ? this.moveCursor + 1 : 0); break; - } + case Button.LEFT: + this.moveSelect = false; + this.setCursor(Page.STATS); + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { + this.hideMoveEffect(); + this.destroyBlinkCursor(); + success = true; + break; + } else { + this.hideMoveSelect(); + success = true; + break; + } } } } else { @@ -546,35 +546,35 @@ export default class SummaryUiHandler extends UiHandler { } else { const pages = Utils.getEnumValues(Page); switch (button) { - case Button.UP: - case Button.DOWN: - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { - break; - } else if (!fromPartyMode) { - break; - } - const isDown = button === Button.DOWN; - const party = this.scene.getParty(); - const partyMemberIndex = this.pokemon ? party.indexOf(this.pokemon) : -1; - if ((isDown && partyMemberIndex < party.length - 1) || (!isDown && partyMemberIndex)) { - const page = this.cursor; - this.clear(); - this.show([ party[partyMemberIndex + (isDown ? 1 : -1)], this.summaryUiMode, page ]); - } - break; - case Button.LEFT: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.RIGHT: - if (this.cursor < pages.length - 1) { - success = this.setCursor(this.cursor + 1); - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.cursor === Page.MOVES) { - this.moveSelect = true; + case Button.UP: + case Button.DOWN: + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { + break; + } else if (!fromPartyMode) { + break; } - } - break; + const isDown = button === Button.DOWN; + const party = this.scene.getParty(); + const partyMemberIndex = this.pokemon ? party.indexOf(this.pokemon) : -1; + if ((isDown && partyMemberIndex < party.length - 1) || (!isDown && partyMemberIndex)) { + const page = this.cursor; + this.clear(); + this.show([ party[partyMemberIndex + (isDown ? 1 : -1)], this.summaryUiMode, page ]); + } + break; + case Button.LEFT: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.RIGHT: + if (this.cursor < pages.length - 1) { + success = this.setCursor(this.cursor + 1); + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.cursor === Page.MOVES) { + this.moveSelect = true; + } + } + break; } } } @@ -730,308 +730,308 @@ export default class SummaryUiHandler extends UiHandler { } switch (page) { - case Page.PROFILE: - const profileContainer = this.scene.add.container(0, -pageBg.height); - pageContainer.add(profileContainer); + case Page.PROFILE: + const profileContainer = this.scene.add.container(0, -pageBg.height); + pageContainer.add(profileContainer); - // TODO: should add field for original trainer name to Pokemon object, to support gift/traded Pokemon from MEs - const trainerText = addBBCodeTextObject(this.scene, 7, 12, `${i18next.t("pokemonSummary:ot")}/${getBBCodeFrag(loggedInUser?.username || i18next.t("pokemonSummary:unknown"), this.scene.gameData.gender === PlayerGender.FEMALE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE)}`, TextStyle.SUMMARY_ALT); - trainerText.setOrigin(0, 0); - profileContainer.add(trainerText); + // TODO: should add field for original trainer name to Pokemon object, to support gift/traded Pokemon from MEs + const trainerText = addBBCodeTextObject(this.scene, 7, 12, `${i18next.t("pokemonSummary:ot")}/${getBBCodeFrag(loggedInUser?.username || i18next.t("pokemonSummary:unknown"), this.scene.gameData.gender === PlayerGender.FEMALE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE)}`, TextStyle.SUMMARY_ALT); + trainerText.setOrigin(0, 0); + profileContainer.add(trainerText); - const trainerIdText = addTextObject(this.scene, 174, 12, this.scene.gameData.trainerId.toString(), TextStyle.SUMMARY_ALT); - trainerIdText.setOrigin(0, 0); - profileContainer.add(trainerIdText); + const trainerIdText = addTextObject(this.scene, 174, 12, this.scene.gameData.trainerId.toString(), TextStyle.SUMMARY_ALT); + trainerIdText.setOrigin(0, 0); + profileContainer.add(trainerIdText); - const typeLabel = addTextObject(this.scene, 7, 28, `${i18next.t("pokemonSummary:type")}/`, TextStyle.WINDOW_ALT); - typeLabel.setOrigin(0, 0); - profileContainer.add(typeLabel); + const typeLabel = addTextObject(this.scene, 7, 28, `${i18next.t("pokemonSummary:type")}/`, TextStyle.WINDOW_ALT); + typeLabel.setOrigin(0, 0); + profileContainer.add(typeLabel); - const getTypeIcon = (index: integer, type: Type, tera: boolean = false) => { - const xCoord = typeLabel.width * typeLabel.scale + 9 + 34 * index; - const typeIcon = !tera - ? this.scene.add.sprite(xCoord, 42, Utils.getLocalizedSpriteKey("types"), Type[type].toLowerCase()) - : this.scene.add.sprite(xCoord, 42, "type_tera"); - if (tera) { - typeIcon.setScale(0.5); - const typeRgb = getTypeRgb(type); - typeIcon.setTint(Phaser.Display.Color.GetColor(typeRgb[0], typeRgb[1], typeRgb[2])); + const getTypeIcon = (index: integer, type: Type, tera: boolean = false) => { + const xCoord = typeLabel.width * typeLabel.scale + 9 + 34 * index; + const typeIcon = !tera + ? this.scene.add.sprite(xCoord, 42, Utils.getLocalizedSpriteKey("types"), Type[type].toLowerCase()) + : this.scene.add.sprite(xCoord, 42, "type_tera"); + if (tera) { + typeIcon.setScale(0.5); + const typeRgb = getTypeRgb(type); + typeIcon.setTint(Phaser.Display.Color.GetColor(typeRgb[0], typeRgb[1], typeRgb[2])); + } + typeIcon.setOrigin(0, 1); + return typeIcon; + }; + + const types = this.pokemon?.getTypes(false, false, true)!; // TODO: is this bang correct? + profileContainer.add(getTypeIcon(0, types[0])); + if (types.length > 1) { + profileContainer.add(getTypeIcon(1, types[1])); + } + if (this.pokemon?.isTerastallized()) { + profileContainer.add(getTypeIcon(types.length, this.pokemon.getTeraType(), true)); } - typeIcon.setOrigin(0, 1); - return typeIcon; - }; - const types = this.pokemon?.getTypes(false, false, true)!; // TODO: is this bang correct? - profileContainer.add(getTypeIcon(0, types[0])); - if (types.length > 1) { - profileContainer.add(getTypeIcon(1, types[1])); - } - if (this.pokemon?.isTerastallized()) { - profileContainer.add(getTypeIcon(types.length, this.pokemon.getTeraType(), true)); - } + if (this.pokemon?.getLuck()) { + const luckLabelText = addTextObject(this.scene, 141, 28, i18next.t("common:luckIndicator"), TextStyle.SUMMARY_ALT); + luckLabelText.setOrigin(0, 0); + profileContainer.add(luckLabelText); - if (this.pokemon?.getLuck()) { - const luckLabelText = addTextObject(this.scene, 141, 28, i18next.t("common:luckIndicator"), TextStyle.SUMMARY_ALT); - luckLabelText.setOrigin(0, 0); - profileContainer.add(luckLabelText); + const luckText = addTextObject(this.scene, 141 + luckLabelText.displayWidth + 2, 28, this.pokemon.getLuck().toString(), TextStyle.SUMMARY); + luckText.setOrigin(0, 0); + luckText.setTint(getVariantTint((Math.min(this.pokemon.getLuck() - 1, 2)) as Variant)); + profileContainer.add(luckText); + } - const luckText = addTextObject(this.scene, 141 + luckLabelText.displayWidth + 2, 28, this.pokemon.getLuck().toString(), TextStyle.SUMMARY); - luckText.setOrigin(0, 0); - luckText.setTint(getVariantTint((Math.min(this.pokemon.getLuck() - 1, 2)) as Variant)); - profileContainer.add(luckText); - } - - this.abilityContainer = { - labelImage: this.scene.add.image(0, 0, "summary_profile_ability"), - ability: this.pokemon?.getAbility(true)!, // TODO: is this bang correct? - nameText: null, - descriptionText: null }; - - const allAbilityInfo = [ this.abilityContainer ]; // Creates an array to iterate through - // Only add to the array and set up displaying a passive if it's unlocked - if (this.pokemon?.hasPassive()) { - this.passiveContainer = { - labelImage: this.scene.add.image(0, 0, "summary_profile_passive"), - ability: this.pokemon.getPassiveAbility(), + this.abilityContainer = { + labelImage: this.scene.add.image(0, 0, "summary_profile_ability"), + ability: this.pokemon?.getAbility(true)!, // TODO: is this bang correct? nameText: null, descriptionText: null }; - allAbilityInfo.push(this.passiveContainer); - // Sets up the pixel button prompt image - this.abilityPrompt = this.scene.add.image(0, 0, !this.scene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a"); - this.abilityPrompt.setPosition(8, 43); - this.abilityPrompt.setVisible(true); - this.abilityPrompt.setOrigin(0, 0); - profileContainer.add(this.abilityPrompt); - } + const allAbilityInfo = [ this.abilityContainer ]; // Creates an array to iterate through + // Only add to the array and set up displaying a passive if it's unlocked + if (this.pokemon?.hasPassive()) { + this.passiveContainer = { + labelImage: this.scene.add.image(0, 0, "summary_profile_passive"), + ability: this.pokemon.getPassiveAbility(), + nameText: null, + descriptionText: null }; + allAbilityInfo.push(this.passiveContainer); - allAbilityInfo.forEach(abilityInfo => { - abilityInfo.labelImage.setPosition(17, 43); - abilityInfo.labelImage.setVisible(true); - abilityInfo.labelImage.setOrigin(0, 0); - profileContainer.add(abilityInfo.labelImage); - - abilityInfo.nameText = addTextObject(this.scene, 7, 66, abilityInfo.ability?.name!, TextStyle.SUMMARY_ALT); // TODO: is this bang correct? - abilityInfo.nameText.setOrigin(0, 1); - profileContainer.add(abilityInfo.nameText); - - abilityInfo.descriptionText = addTextObject(this.scene, 7, 69, abilityInfo.ability?.description!, TextStyle.WINDOW_ALT, { wordWrap: { width: 1224 }}); // TODO: is this bang correct? - abilityInfo.descriptionText.setOrigin(0, 0); - profileContainer.add(abilityInfo.descriptionText); - - // Sets up the mask that hides the description text to give an illusion of scrolling - const descriptionTextMaskRect = this.scene.make.graphics({}); - descriptionTextMaskRect.setScale(6); - descriptionTextMaskRect.fillStyle(0xFFFFFF); - descriptionTextMaskRect.beginPath(); - descriptionTextMaskRect.fillRect(110, 90.5, 206, 31); - - const abilityDescriptionTextMask = descriptionTextMaskRect.createGeometryMask(); - - abilityInfo.descriptionText.setMask(abilityDescriptionTextMask); - - const abilityDescriptionLineCount = Math.floor(abilityInfo.descriptionText.displayHeight / 14.83); - - // Animates the description text moving upwards - if (abilityDescriptionLineCount > 2) { - abilityInfo.descriptionText.setY(69); - this.descriptionScrollTween = this.scene.tweens.add({ - targets: abilityInfo.descriptionText, - delay: Utils.fixedInt(2000), - loop: -1, - hold: Utils.fixedInt(2000), - duration: Utils.fixedInt((abilityDescriptionLineCount - 2) * 2000), - y: `-=${14.83 * (abilityDescriptionLineCount - 2)}` - }); + // Sets up the pixel button prompt image + this.abilityPrompt = this.scene.add.image(0, 0, !this.scene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a"); + this.abilityPrompt.setPosition(8, 43); + this.abilityPrompt.setVisible(true); + this.abilityPrompt.setOrigin(0, 0); + profileContainer.add(this.abilityPrompt); } - }); - // Turn off visibility of passive info by default - this.passiveContainer?.labelImage.setVisible(false); - this.passiveContainer?.nameText?.setVisible(false); - this.passiveContainer?.descriptionText?.setVisible(false); - const closeFragment = getBBCodeFrag("", TextStyle.WINDOW_ALT); - const rawNature = Utils.toReadableString(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct? - const nature = `${getBBCodeFrag(Utils.toReadableString(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct? + allAbilityInfo.forEach(abilityInfo => { + abilityInfo.labelImage.setPosition(17, 43); + abilityInfo.labelImage.setVisible(true); + abilityInfo.labelImage.setOrigin(0, 0); + profileContainer.add(abilityInfo.labelImage); - const memoString = i18next.t("pokemonSummary:memoString", { - metFragment: i18next.t(`pokemonSummary:metFragment.${this.pokemon?.metBiome === -1 ? "apparently" : "normal"}`, { - biome: `${getBBCodeFrag(getBiomeName(this.pokemon?.metBiome!), TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? - level: `${getBBCodeFrag(this.pokemon?.metLevel.toString()!, TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? - wave: `${getBBCodeFrag((this.pokemon?.metWave ? this.pokemon.metWave.toString()! : i18next.t("pokemonSummary:unknownTrainer")), TextStyle.SUMMARY_RED)}${closeFragment}`, - }), - natureFragment: i18next.t(`pokemonSummary:natureFragment.${rawNature}`, { nature: nature }) - }); + abilityInfo.nameText = addTextObject(this.scene, 7, 66, abilityInfo.ability?.name!, TextStyle.SUMMARY_ALT); // TODO: is this bang correct? + abilityInfo.nameText.setOrigin(0, 1); + profileContainer.add(abilityInfo.nameText); - const memoText = addBBCodeTextObject(this.scene, 7, 113, String(memoString), TextStyle.WINDOW_ALT); - memoText.setOrigin(0, 0); - profileContainer.add(memoText); - break; - case Page.STATS: - const statsContainer = this.scene.add.container(0, -pageBg.height); - pageContainer.add(statsContainer); + abilityInfo.descriptionText = addTextObject(this.scene, 7, 69, abilityInfo.ability?.description!, TextStyle.WINDOW_ALT, { wordWrap: { width: 1224 }}); // TODO: is this bang correct? + abilityInfo.descriptionText.setOrigin(0, 0); + profileContainer.add(abilityInfo.descriptionText); - PERMANENT_STATS.forEach((stat, s) => { - const statName = i18next.t(getStatKey(stat)); - const rowIndex = s % 3; - const colIndex = Math.floor(s / 3); + // Sets up the mask that hides the description text to give an illusion of scrolling + const descriptionTextMaskRect = this.scene.make.graphics({}); + descriptionTextMaskRect.setScale(6); + descriptionTextMaskRect.fillStyle(0xFFFFFF); + descriptionTextMaskRect.beginPath(); + descriptionTextMaskRect.fillRect(110, 90.5, 206, 31); - const natureStatMultiplier = getNatureStatMultiplier(this.pokemon?.getNature()!, s); // TODO: is this bang correct? + const abilityDescriptionTextMask = descriptionTextMaskRect.createGeometryMask(); - const statLabel = addTextObject(this.scene, 27 + 115 * colIndex + (colIndex === 1 ? 5 : 0), 56 + 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE); - statLabel.setOrigin(0.5, 0); - statsContainer.add(statLabel); + abilityInfo.descriptionText.setMask(abilityDescriptionTextMask); - const statValueText = stat !== Stat.HP - ? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct? - : `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct? + const abilityDescriptionLineCount = Math.floor(abilityInfo.descriptionText.displayHeight / 14.83); - const statValue = addTextObject(this.scene, 120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); - statValue.setOrigin(1, 0); - statsContainer.add(statValue); - }); + // Animates the description text moving upwards + if (abilityDescriptionLineCount > 2) { + abilityInfo.descriptionText.setY(69); + this.descriptionScrollTween = this.scene.tweens.add({ + targets: abilityInfo.descriptionText, + delay: Utils.fixedInt(2000), + loop: -1, + hold: Utils.fixedInt(2000), + duration: Utils.fixedInt((abilityDescriptionLineCount - 2) * 2000), + y: `-=${14.83 * (abilityDescriptionLineCount - 2)}` + }); + } + }); + // Turn off visibility of passive info by default + this.passiveContainer?.labelImage.setVisible(false); + this.passiveContainer?.nameText?.setVisible(false); + this.passiveContainer?.descriptionText?.setVisible(false); - const itemModifiers = (this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const closeFragment = getBBCodeFrag("", TextStyle.WINDOW_ALT); + const rawNature = Utils.toReadableString(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct? + const nature = `${getBBCodeFrag(Utils.toReadableString(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct? + + const memoString = i18next.t("pokemonSummary:memoString", { + metFragment: i18next.t(`pokemonSummary:metFragment.${this.pokemon?.metBiome === -1 ? "apparently" : "normal"}`, { + biome: `${getBBCodeFrag(getBiomeName(this.pokemon?.metBiome!), TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? + level: `${getBBCodeFrag(this.pokemon?.metLevel.toString()!, TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? + wave: `${getBBCodeFrag((this.pokemon?.metWave ? this.pokemon.metWave.toString()! : i18next.t("pokemonSummary:unknownTrainer")), TextStyle.SUMMARY_RED)}${closeFragment}`, + }), + natureFragment: i18next.t(`pokemonSummary:natureFragment.${rawNature}`, { nature: nature }) + }); + + const memoText = addBBCodeTextObject(this.scene, 7, 113, String(memoString), TextStyle.WINDOW_ALT); + memoText.setOrigin(0, 0); + profileContainer.add(memoText); + break; + case Page.STATS: + const statsContainer = this.scene.add.container(0, -pageBg.height); + pageContainer.add(statsContainer); + + PERMANENT_STATS.forEach((stat, s) => { + const statName = i18next.t(getStatKey(stat)); + const rowIndex = s % 3; + const colIndex = Math.floor(s / 3); + + const natureStatMultiplier = getNatureStatMultiplier(this.pokemon?.getNature()!, s); // TODO: is this bang correct? + + const statLabel = addTextObject(this.scene, 27 + 115 * colIndex + (colIndex === 1 ? 5 : 0), 56 + 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE); + statLabel.setOrigin(0.5, 0); + statsContainer.add(statLabel); + + const statValueText = stat !== Stat.HP + ? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct? + : `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct? + + const statValue = addTextObject(this.scene, 120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); + statValue.setOrigin(1, 0); + statsContainer.add(statValue); + }); + + const itemModifiers = (this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.pokemon?.id, this.playerParty) as PokemonHeldItemModifier[]) - .sort(modifierSortFunc); + .sort(modifierSortFunc); - itemModifiers.forEach((item, i) => { - const icon = item.getIcon(this.scene, true); + itemModifiers.forEach((item, i) => { + const icon = item.getIcon(this.scene, true); - icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); - statsContainer.add(icon); + icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); + statsContainer.add(icon); - icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); - icon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip(item.type.name, item.type.getDescription(this.scene), true)); - icon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); - }); + icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); + icon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip(item.type.name, item.type.getDescription(this.scene), true)); + icon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + }); - const pkmLvl = this.pokemon?.level!; // TODO: is this bang correct? - const pkmLvlExp = this.pokemon?.levelExp!; // TODO: is this bang correct? - const pkmExp = this.pokemon?.exp!; // TODO: is this bang correct? - const pkmSpeciesGrowthRate = this.pokemon?.species.growthRate!; // TODO: is this bang correct? - const relLvExp = getLevelRelExp(pkmLvl + 1, pkmSpeciesGrowthRate); - const expRatio = pkmLvl < this.scene.getMaxExpLevel() ? pkmLvlExp / relLvExp : 0; + const pkmLvl = this.pokemon?.level!; // TODO: is this bang correct? + const pkmLvlExp = this.pokemon?.levelExp!; // TODO: is this bang correct? + const pkmExp = this.pokemon?.exp!; // TODO: is this bang correct? + const pkmSpeciesGrowthRate = this.pokemon?.species.growthRate!; // TODO: is this bang correct? + const relLvExp = getLevelRelExp(pkmLvl + 1, pkmSpeciesGrowthRate); + const expRatio = pkmLvl < this.scene.getMaxExpLevel() ? pkmLvlExp / relLvExp : 0; - const expLabel = addTextObject(this.scene, 6, 112, i18next.t("pokemonSummary:expPoints"), TextStyle.SUMMARY); - expLabel.setOrigin(0, 0); - statsContainer.add(expLabel); + const expLabel = addTextObject(this.scene, 6, 112, i18next.t("pokemonSummary:expPoints"), TextStyle.SUMMARY); + expLabel.setOrigin(0, 0); + statsContainer.add(expLabel); - const nextLvExpLabel = addTextObject(this.scene, 6, 128, i18next.t("pokemonSummary:nextLv"), TextStyle.SUMMARY); - nextLvExpLabel.setOrigin(0, 0); - statsContainer.add(nextLvExpLabel); + const nextLvExpLabel = addTextObject(this.scene, 6, 128, i18next.t("pokemonSummary:nextLv"), TextStyle.SUMMARY); + nextLvExpLabel.setOrigin(0, 0); + statsContainer.add(nextLvExpLabel); - const expText = addTextObject(this.scene, 208, 112, pkmExp.toString(), TextStyle.WINDOW_ALT); - expText.setOrigin(1, 0); - statsContainer.add(expText); + const expText = addTextObject(this.scene, 208, 112, pkmExp.toString(), TextStyle.WINDOW_ALT); + expText.setOrigin(1, 0); + statsContainer.add(expText); - const nextLvExp = pkmLvl < this.scene.getMaxExpLevel() - ? getLevelTotalExp(pkmLvl + 1, pkmSpeciesGrowthRate) - pkmExp - : 0; - const nextLvExpText = addTextObject(this.scene, 208, 128, nextLvExp.toString(), TextStyle.WINDOW_ALT); - nextLvExpText.setOrigin(1, 0); - statsContainer.add(nextLvExpText); + const nextLvExp = pkmLvl < this.scene.getMaxExpLevel() + ? getLevelTotalExp(pkmLvl + 1, pkmSpeciesGrowthRate) - pkmExp + : 0; + const nextLvExpText = addTextObject(this.scene, 208, 128, nextLvExp.toString(), TextStyle.WINDOW_ALT); + nextLvExpText.setOrigin(1, 0); + statsContainer.add(nextLvExpText); - const expOverlay = this.scene.add.image(140, 145, "summary_stats_overlay_exp"); - expOverlay.setOrigin(0, 0); - statsContainer.add(expOverlay); + const expOverlay = this.scene.add.image(140, 145, "summary_stats_overlay_exp"); + expOverlay.setOrigin(0, 0); + statsContainer.add(expOverlay); - const expMaskRect = this.scene.make.graphics({}); - expMaskRect.setScale(6); - expMaskRect.fillStyle(0xFFFFFF); - expMaskRect.beginPath(); - expMaskRect.fillRect(140 + pageContainer.x, 145 + pageContainer.y + 21, Math.floor(expRatio * 64), 3); + const expMaskRect = this.scene.make.graphics({}); + expMaskRect.setScale(6); + expMaskRect.fillStyle(0xFFFFFF); + expMaskRect.beginPath(); + expMaskRect.fillRect(140 + pageContainer.x, 145 + pageContainer.y + 21, Math.floor(expRatio * 64), 3); - const expMask = expMaskRect.createGeometryMask(); + const expMask = expMaskRect.createGeometryMask(); - expOverlay.setMask(expMask); - break; - case Page.MOVES: - this.movesContainer = this.scene.add.container(5, -pageBg.height + 26); - pageContainer.add(this.movesContainer); + expOverlay.setMask(expMask); + break; + case Page.MOVES: + this.movesContainer = this.scene.add.container(5, -pageBg.height + 26); + pageContainer.add(this.movesContainer); - this.extraMoveRowContainer = this.scene.add.container(0, 64); - this.extraMoveRowContainer.setVisible(false); - this.movesContainer.add(this.extraMoveRowContainer); + this.extraMoveRowContainer = this.scene.add.container(0, 64); + this.extraMoveRowContainer.setVisible(false); + this.movesContainer.add(this.extraMoveRowContainer); - const extraRowOverlay = this.scene.add.image(-2, 1, "summary_moves_overlay_row"); - extraRowOverlay.setOrigin(0, 1); - this.extraMoveRowContainer.add(extraRowOverlay); + const extraRowOverlay = this.scene.add.image(-2, 1, "summary_moves_overlay_row"); + extraRowOverlay.setOrigin(0, 1); + this.extraMoveRowContainer.add(extraRowOverlay); - const extraRowText = addTextObject(this.scene, 35, 0, this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.newMove ? this.newMove.name : i18next.t("pokemonSummary:cancel"), - this.summaryUiMode === SummaryUiMode.LEARN_MOVE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY); - extraRowText.setOrigin(0, 1); - this.extraMoveRowContainer.add(extraRowText); + const extraRowText = addTextObject(this.scene, 35, 0, this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.newMove ? this.newMove.name : i18next.t("pokemonSummary:cancel"), + this.summaryUiMode === SummaryUiMode.LEARN_MOVE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY); + extraRowText.setOrigin(0, 1); + this.extraMoveRowContainer.add(extraRowText); - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { - this.extraMoveRowContainer.setVisible(true); + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { + this.extraMoveRowContainer.setVisible(true); - if (this.newMove && this.pokemon) { - const spriteKey = Utils.getLocalizedSpriteKey("types"); - const moveType = this.pokemon.getMoveType(this.newMove); - const newMoveTypeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); - newMoveTypeIcon.setOrigin(0, 1); - this.extraMoveRowContainer.add(newMoveTypeIcon); - } - const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); - ppOverlay.setOrigin(0, 1); - this.extraMoveRowContainer.add(ppOverlay); + if (this.newMove && this.pokemon) { + const spriteKey = Utils.getLocalizedSpriteKey("types"); + const moveType = this.pokemon.getMoveType(this.newMove); + const newMoveTypeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); + newMoveTypeIcon.setOrigin(0, 1); + this.extraMoveRowContainer.add(newMoveTypeIcon); + } + const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); + ppOverlay.setOrigin(0, 1); + this.extraMoveRowContainer.add(ppOverlay); - const pp = Utils.padInt(this.newMove?.pp!, 2, " "); // TODO: is this bang correct? - const ppText = addTextObject(this.scene, 173, 1, `${pp}/${pp}`, TextStyle.WINDOW); - ppText.setOrigin(0, 1); - this.extraMoveRowContainer.add(ppText); - } - - this.moveRowsContainer = this.scene.add.container(0, 0); - this.movesContainer.add(this.moveRowsContainer); - - for (let m = 0; m < 4; m++) { - const move: PokemonMove | null = this.pokemon && this.pokemon.moveset.length > m ? this.pokemon?.moveset[m] : null; - const moveRowContainer = this.scene.add.container(0, 16 * m); - this.moveRowsContainer.add(moveRowContainer); - - if (move && this.pokemon) { - const spriteKey = Utils.getLocalizedSpriteKey("types"); - const moveType = this.pokemon.getMoveType(move.getMove()); - const typeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); - typeIcon.setOrigin(0, 1); - moveRowContainer.add(typeIcon); + const pp = Utils.padInt(this.newMove?.pp!, 2, " "); // TODO: is this bang correct? + const ppText = addTextObject(this.scene, 173, 1, `${pp}/${pp}`, TextStyle.WINDOW); + ppText.setOrigin(0, 1); + this.extraMoveRowContainer.add(ppText); } - const moveText = addTextObject(this.scene, 35, 0, move ? move.getName() : "-", TextStyle.SUMMARY); - moveText.setOrigin(0, 1); - moveRowContainer.add(moveText); + this.moveRowsContainer = this.scene.add.container(0, 0); + this.movesContainer.add(this.moveRowsContainer); - const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); - ppOverlay.setOrigin(0, 1); - moveRowContainer.add(ppOverlay); + for (let m = 0; m < 4; m++) { + const move: PokemonMove | null = this.pokemon && this.pokemon.moveset.length > m ? this.pokemon?.moveset[m] : null; + const moveRowContainer = this.scene.add.container(0, 16 * m); + this.moveRowsContainer.add(moveRowContainer); - const ppText = addTextObject(this.scene, 173, 1, "--/--", TextStyle.WINDOW); - ppText.setOrigin(0, 1); + if (move && this.pokemon) { + const spriteKey = Utils.getLocalizedSpriteKey("types"); + const moveType = this.pokemon.getMoveType(move.getMove()); + const typeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); + typeIcon.setOrigin(0, 1); + moveRowContainer.add(typeIcon); + } - if (move) { - const maxPP = move.getMovePp(); - const pp = maxPP - move.ppUsed; - ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`); + const moveText = addTextObject(this.scene, 35, 0, move ? move.getName() : "-", TextStyle.SUMMARY); + moveText.setOrigin(0, 1); + moveRowContainer.add(moveText); + + const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); + ppOverlay.setOrigin(0, 1); + moveRowContainer.add(ppOverlay); + + const ppText = addTextObject(this.scene, 173, 1, "--/--", TextStyle.WINDOW); + ppText.setOrigin(0, 1); + + if (move) { + const maxPP = move.getMovePp(); + const pp = maxPP - move.ppUsed; + ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`); + } + + moveRowContainer.add(ppText); } - moveRowContainer.add(ppText); - } + this.moveDescriptionText = addTextObject(this.scene, 2, 84, "", TextStyle.WINDOW_ALT, { wordWrap: { width: 1212 }}); + this.movesContainer.add(this.moveDescriptionText); - this.moveDescriptionText = addTextObject(this.scene, 2, 84, "", TextStyle.WINDOW_ALT, { wordWrap: { width: 1212 }}); - this.movesContainer.add(this.moveDescriptionText); + const moveDescriptionTextMaskRect = this.scene.make.graphics({}); + moveDescriptionTextMaskRect.setScale(6); + moveDescriptionTextMaskRect.fillStyle(0xFFFFFF); + moveDescriptionTextMaskRect.beginPath(); + moveDescriptionTextMaskRect.fillRect(112, 130, 202, 46); - const moveDescriptionTextMaskRect = this.scene.make.graphics({}); - moveDescriptionTextMaskRect.setScale(6); - moveDescriptionTextMaskRect.fillStyle(0xFFFFFF); - moveDescriptionTextMaskRect.beginPath(); - moveDescriptionTextMaskRect.fillRect(112, 130, 202, 46); + const moveDescriptionTextMask = moveDescriptionTextMaskRect.createGeometryMask(); - const moveDescriptionTextMask = moveDescriptionTextMaskRect.createGeometryMask(); - - this.moveDescriptionText.setMask(moveDescriptionTextMask); - break; + this.moveDescriptionText.setMask(moveDescriptionTextMask); + break; } } diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index 85b671c2617..4c55a4b960e 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -71,26 +71,26 @@ export default class TargetSelectUiHandler extends UiHandler { success = false; } else { switch (button) { - case Button.UP: - if (this.cursor < BattlerIndex.ENEMY && this.targets.findIndex(t => t >= BattlerIndex.ENEMY) > -1) { - success = this.setCursor(this.targets.find(t => t >= BattlerIndex.ENEMY)!); // TODO: is the bang correct here? - } - break; - case Button.DOWN: - if (this.cursor >= BattlerIndex.ENEMY && this.targets.findIndex(t => t < BattlerIndex.ENEMY) > -1) { - success = this.setCursor(this.targets.find(t => t < BattlerIndex.ENEMY)!); // TODO: is the bang correct here? - } - break; - case Button.LEFT: - if (this.cursor % 2 && this.targets.findIndex(t => t === this.cursor - 1) > -1) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.RIGHT: - if (!(this.cursor % 2) && this.targets.findIndex(t => t === this.cursor + 1) > -1) { - success = this.setCursor(this.cursor + 1); - } - break; + case Button.UP: + if (this.cursor < BattlerIndex.ENEMY && this.targets.findIndex(t => t >= BattlerIndex.ENEMY) > -1) { + success = this.setCursor(this.targets.find(t => t >= BattlerIndex.ENEMY)!); // TODO: is the bang correct here? + } + break; + case Button.DOWN: + if (this.cursor >= BattlerIndex.ENEMY && this.targets.findIndex(t => t < BattlerIndex.ENEMY) > -1) { + success = this.setCursor(this.targets.find(t => t < BattlerIndex.ENEMY)!); // TODO: is the bang correct here? + } + break; + case Button.LEFT: + if (this.cursor % 2 && this.targets.findIndex(t => t === this.cursor - 1) > -1) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.RIGHT: + if (!(this.cursor % 2) && this.targets.findIndex(t => t === this.cursor + 1) > -1) { + success = this.setCursor(this.cursor + 1); + } + break; } } diff --git a/src/ui/text.ts b/src/ui/text.ts index e6e1978118b..069aa8680fc 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -129,84 +129,84 @@ export function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraSty } switch (style) { - case TextStyle.SUMMARY: - case TextStyle.SUMMARY_ALT: - case TextStyle.SUMMARY_BLUE: - case TextStyle.SUMMARY_RED: - case TextStyle.SUMMARY_PINK: - case TextStyle.SUMMARY_GOLD: - case TextStyle.SUMMARY_GRAY: - case TextStyle.SUMMARY_GREEN: - case TextStyle.WINDOW: - case TextStyle.WINDOW_ALT: - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.STATS_LABEL: - let fontSizeLabel = "96px"; - switch (lang) { - case "de": + case TextStyle.SUMMARY: + case TextStyle.SUMMARY_ALT: + case TextStyle.SUMMARY_BLUE: + case TextStyle.SUMMARY_RED: + case TextStyle.SUMMARY_PINK: + case TextStyle.SUMMARY_GOLD: + case TextStyle.SUMMARY_GRAY: + case TextStyle.SUMMARY_GREEN: + case TextStyle.WINDOW: + case TextStyle.WINDOW_ALT: shadowXpos = 3; shadowYpos = 3; - fontSizeLabel = "80px"; break; - default: - fontSizeLabel = "96px"; + case TextStyle.STATS_LABEL: + let fontSizeLabel = "96px"; + switch (lang) { + case "de": + shadowXpos = 3; + shadowYpos = 3; + fontSizeLabel = "80px"; + break; + default: + fontSizeLabel = "96px"; + break; + } + styleOptions.fontSize = fontSizeLabel; break; - } - styleOptions.fontSize = fontSizeLabel; - break; - case TextStyle.STATS_VALUE: - shadowXpos = 3; - shadowYpos = 3; - let fontSizeValue = "96px"; - switch (lang) { - case "de": - fontSizeValue = "80px"; + case TextStyle.STATS_VALUE: + shadowXpos = 3; + shadowYpos = 3; + let fontSizeValue = "96px"; + switch (lang) { + case "de": + fontSizeValue = "80px"; + break; + default: + fontSizeValue = "96px"; + break; + } + styleOptions.fontSize = fontSizeValue; break; - default: - fontSizeValue = "96px"; + case TextStyle.MESSAGE: + case TextStyle.SETTINGS_LABEL: + case TextStyle.SETTINGS_LOCKED: + case TextStyle.SETTINGS_SELECTED: + break; + case TextStyle.BATTLE_INFO: + case TextStyle.MONEY: + case TextStyle.TOOLTIP_TITLE: + styleOptions.fontSize = defaultFontSize - 24; + shadowXpos = 3.5; + shadowYpos = 3.5; + break; + case TextStyle.PARTY: + case TextStyle.PARTY_RED: + styleOptions.fontSize = defaultFontSize - 30; + styleOptions.fontFamily = "pkmnems"; + break; + case TextStyle.TOOLTIP_CONTENT: + styleOptions.fontSize = defaultFontSize - 32; + shadowXpos = 3; + shadowYpos = 3; + break; + case TextStyle.MOVE_INFO_CONTENT: + styleOptions.fontSize = defaultFontSize - 40; + shadowXpos = 3; + shadowYpos = 3; + break; + case TextStyle.SMALLER_WINDOW_ALT: + styleOptions.fontSize = defaultFontSize - 36; + shadowXpos = 3; + shadowYpos = 3; + break; + case TextStyle.BGM_BAR: + styleOptions.fontSize = defaultFontSize - 24; + shadowXpos = 3; + shadowYpos = 3; break; - } - styleOptions.fontSize = fontSizeValue; - break; - case TextStyle.MESSAGE: - case TextStyle.SETTINGS_LABEL: - case TextStyle.SETTINGS_LOCKED: - case TextStyle.SETTINGS_SELECTED: - break; - case TextStyle.BATTLE_INFO: - case TextStyle.MONEY: - case TextStyle.TOOLTIP_TITLE: - styleOptions.fontSize = defaultFontSize - 24; - shadowXpos = 3.5; - shadowYpos = 3.5; - break; - case TextStyle.PARTY: - case TextStyle.PARTY_RED: - styleOptions.fontSize = defaultFontSize - 30; - styleOptions.fontFamily = "pkmnems"; - break; - case TextStyle.TOOLTIP_CONTENT: - styleOptions.fontSize = defaultFontSize - 32; - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.MOVE_INFO_CONTENT: - styleOptions.fontSize = defaultFontSize - 40; - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.SMALLER_WINDOW_ALT: - styleOptions.fontSize = defaultFontSize - 36; - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.BGM_BAR: - styleOptions.fontSize = defaultFontSize - 24; - shadowXpos = 3; - shadowYpos = 3; - break; } const shadowColor = getTextColor(style, true, uiTheme); @@ -257,110 +257,110 @@ export function getTextWithColors(content: string, primaryStyle: TextStyle, uiTh export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: UiTheme = UiTheme.DEFAULT): string { const isLegacyTheme = uiTheme === UiTheme.LEGACY; switch (textStyle) { - case TextStyle.MESSAGE: - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.WINDOW: - case TextStyle.MOVE_INFO_CONTENT: - case TextStyle.MOVE_PP_FULL: - case TextStyle.TOOLTIP_CONTENT: - case TextStyle.SETTINGS_VALUE: - if (isLegacyTheme) { + case TextStyle.MESSAGE: + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.WINDOW: + case TextStyle.MOVE_INFO_CONTENT: + case TextStyle.MOVE_PP_FULL: + case TextStyle.TOOLTIP_CONTENT: + case TextStyle.SETTINGS_VALUE: + if (isLegacyTheme) { + return !shadow ? "#484848" : "#d0d0c8"; + } + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.MOVE_PP_HALF_FULL: + if (isLegacyTheme) { + return !shadow ? "#a68e17" : "#ebd773"; + } + return !shadow ? "#ccbe00" : "#6e672c"; + case TextStyle.MOVE_PP_NEAR_EMPTY: + if (isLegacyTheme) { + return !shadow ? "#d64b00" : "#f7b18b"; + } + return !shadow ? "#d64b00" : "#69402a"; + case TextStyle.MOVE_PP_EMPTY: + if (isLegacyTheme) { + return !shadow ? "#e13d3d" : "#fca2a2"; + } + return !shadow ? "#e13d3d" : "#632929"; + case TextStyle.WINDOW_ALT: return !shadow ? "#484848" : "#d0d0c8"; - } - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.MOVE_PP_HALF_FULL: - if (isLegacyTheme) { - return !shadow ? "#a68e17" : "#ebd773"; - } - return !shadow ? "#ccbe00" : "#6e672c"; - case TextStyle.MOVE_PP_NEAR_EMPTY: - if (isLegacyTheme) { - return !shadow ? "#d64b00" : "#f7b18b"; - } - return !shadow ? "#d64b00" : "#69402a"; - case TextStyle.MOVE_PP_EMPTY: - if (isLegacyTheme) { - return !shadow ? "#e13d3d" : "#fca2a2"; - } - return !shadow ? "#e13d3d" : "#632929"; - case TextStyle.WINDOW_ALT: - return !shadow ? "#484848" : "#d0d0c8"; - case TextStyle.BATTLE_INFO: - if (isLegacyTheme) { - return !shadow ? "#404040" : "#ded6b5"; - } - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.PARTY: - return !shadow ? "#f8f8f8" : "#707070"; - case TextStyle.PARTY_RED: - return !shadow ? "#f89890" : "#984038"; - case TextStyle.SUMMARY: - return !shadow ? "#f8f8f8" : "#636363"; - case TextStyle.SUMMARY_ALT: - if (isLegacyTheme) { + case TextStyle.BATTLE_INFO: + if (isLegacyTheme) { + return !shadow ? "#404040" : "#ded6b5"; + } + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.PARTY: + return !shadow ? "#f8f8f8" : "#707070"; + case TextStyle.PARTY_RED: + return !shadow ? "#f89890" : "#984038"; + case TextStyle.SUMMARY: return !shadow ? "#f8f8f8" : "#636363"; - } - return !shadow ? "#484848" : "#d0d0c8"; - case TextStyle.SUMMARY_RED: - case TextStyle.TOOLTIP_TITLE: - return !shadow ? "#e70808" : "#ffbd73"; - case TextStyle.SUMMARY_BLUE: - return !shadow ? "#40c8f8" : "#006090"; - case TextStyle.SUMMARY_PINK: - return !shadow ? "#f89890" : "#984038"; - case TextStyle.SUMMARY_GOLD: - case TextStyle.MONEY: - return !shadow ? "#e8e8a8" : "#a0a060"; - case TextStyle.SETTINGS_LOCKED: - case TextStyle.SUMMARY_GRAY: - return !shadow ? "#a0a0a0" : "#636363"; - case TextStyle.STATS_LABEL: - return !shadow ? "#f8b050" : "#c07800"; - case TextStyle.STATS_VALUE: - if (isLegacyTheme) { + case TextStyle.SUMMARY_ALT: + if (isLegacyTheme) { + return !shadow ? "#f8f8f8" : "#636363"; + } return !shadow ? "#484848" : "#d0d0c8"; - } - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.SUMMARY_GREEN: - return !shadow ? "#78c850" : "#306850"; - case TextStyle.SETTINGS_LABEL: - case TextStyle.PERFECT_IV: - return !shadow ? "#f8b050" : "#c07800"; - case TextStyle.SETTINGS_SELECTED: - return !shadow ? "#f88880" : "#f83018"; - case TextStyle.SMALLER_WINDOW_ALT: - return !shadow ? "#484848" : "#d0d0c8"; - case TextStyle.BGM_BAR: - return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.SUMMARY_RED: + case TextStyle.TOOLTIP_TITLE: + return !shadow ? "#e70808" : "#ffbd73"; + case TextStyle.SUMMARY_BLUE: + return !shadow ? "#40c8f8" : "#006090"; + case TextStyle.SUMMARY_PINK: + return !shadow ? "#f89890" : "#984038"; + case TextStyle.SUMMARY_GOLD: + case TextStyle.MONEY: + return !shadow ? "#e8e8a8" : "#a0a060"; + case TextStyle.SETTINGS_LOCKED: + case TextStyle.SUMMARY_GRAY: + return !shadow ? "#a0a0a0" : "#636363"; + case TextStyle.STATS_LABEL: + return !shadow ? "#f8b050" : "#c07800"; + case TextStyle.STATS_VALUE: + if (isLegacyTheme) { + return !shadow ? "#484848" : "#d0d0c8"; + } + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.SUMMARY_GREEN: + return !shadow ? "#78c850" : "#306850"; + case TextStyle.SETTINGS_LABEL: + case TextStyle.PERFECT_IV: + return !shadow ? "#f8b050" : "#c07800"; + case TextStyle.SETTINGS_SELECTED: + return !shadow ? "#f88880" : "#f83018"; + case TextStyle.SMALLER_WINDOW_ALT: + return !shadow ? "#484848" : "#d0d0c8"; + case TextStyle.BGM_BAR: + return !shadow ? "#f8f8f8" : "#6b5a73"; } } export function getModifierTierTextTint(tier: ModifierTier): integer { switch (tier) { - case ModifierTier.COMMON: - return 0xf8f8f8; - case ModifierTier.GREAT: - return 0x4998f8; - case ModifierTier.ULTRA: - return 0xf8d038; - case ModifierTier.ROGUE: - return 0xdb4343; - case ModifierTier.MASTER: - return 0xe331c5; - case ModifierTier.LUXURY: - return 0xe74c18; + case ModifierTier.COMMON: + return 0xf8f8f8; + case ModifierTier.GREAT: + return 0x4998f8; + case ModifierTier.ULTRA: + return 0xf8d038; + case ModifierTier.ROGUE: + return 0xdb4343; + case ModifierTier.MASTER: + return 0xe331c5; + case ModifierTier.LUXURY: + return 0xe74c18; } } export function getEggTierTextTint(tier: EggTier): integer { switch (tier) { - case EggTier.COMMON: - return getModifierTierTextTint(ModifierTier.COMMON); - case EggTier.GREAT: - return getModifierTierTextTint(ModifierTier.GREAT); - case EggTier.ULTRA: - return getModifierTierTextTint(ModifierTier.ULTRA); - case EggTier.MASTER: - return getModifierTierTextTint(ModifierTier.MASTER); + case EggTier.COMMON: + return getModifierTierTextTint(ModifierTier.COMMON); + case EggTier.RARE: + return getModifierTierTextTint(ModifierTier.GREAT); + case EggTier.EPIC: + return getModifierTierTextTint(ModifierTier.ULTRA); + case EggTier.LEGENDARY: + return getModifierTierTextTint(ModifierTier.MASTER); } } diff --git a/src/ui/ui-theme.ts b/src/ui/ui-theme.ts index 8ff50667248..89c56384bd0 100644 --- a/src/ui/ui-theme.ts +++ b/src/ui/ui-theme.ts @@ -10,12 +10,12 @@ export enum WindowVariant { export function getWindowVariantSuffix(windowVariant: WindowVariant): string { switch (windowVariant) { - case WindowVariant.THIN: - return "_thin"; - case WindowVariant.XTHIN: - return "_xthin"; - default: - return ""; + case WindowVariant.THIN: + return "_thin"; + case WindowVariant.XTHIN: + return "_xthin"; + default: + return ""; } } diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 373930c5d84..63cd48ab1cd 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -34,7 +34,6 @@ import SaveSlotSelectUiHandler from "./save-slot-select-ui-handler"; import TitleUiHandler from "./title-ui-handler"; import SavingIconHandler from "./saving-icon-handler"; import UnavailableModalUiHandler from "./unavailable-modal-ui-handler"; -import OutdatedModalUiHandler from "./outdated-modal-ui-handler"; import SessionReloadModalUiHandler from "./session-reload-modal-ui-handler"; import { Button } from "#enums/buttons"; import i18next from "i18next"; @@ -90,7 +89,6 @@ export enum Mode { LOADING, SESSION_RELOAD, UNAVAILABLE, - OUTDATED, CHALLENGE_SELECT, RENAME_POKEMON, RUN_HISTORY, @@ -134,7 +132,6 @@ const noTransitionModes = [ Mode.LOADING, Mode.SESSION_RELOAD, Mode.UNAVAILABLE, - Mode.OUTDATED, Mode.RENAME_POKEMON, Mode.TEST_DIALOGUE, Mode.AUTO_COMPLETE, @@ -200,7 +197,6 @@ export default class UI extends Phaser.GameObjects.Container { new LoadingModalUiHandler(scene), new SessionReloadModalUiHandler(scene), new UnavailableModalUiHandler(scene), - new OutdatedModalUiHandler(scene), new GameChallengesUiHandler(scene), new RenameFormUiHandler(scene), new RunHistoryUiHandler(scene), diff --git a/src/utils.ts b/src/utils.ts index 9cc95b00826..8a35a4b3f07 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -38,10 +38,6 @@ export function shiftCharCodes(str: string, shiftCount: integer) { return newStr; } -export function clampInt(value: integer, min: integer, max: integer): integer { - return Math.min(Math.max(value, min), max); -} - export function randGauss(stdev: number, mean: number = 0): number { if (!stdev) { return 0; @@ -198,23 +194,23 @@ export function formatLargeNumber(count: integer, threshold: integer): string { const ret = count.toString(); let suffix = ""; switch (Math.ceil(ret.length / 3) - 1) { - case 1: - suffix = "K"; - break; - case 2: - suffix = "M"; - break; - case 3: - suffix = "B"; - break; - case 4: - suffix = "T"; - break; - case 5: - suffix = "q"; - break; - default: - return "?"; + case 1: + suffix = "K"; + break; + case 2: + suffix = "M"; + break; + case 3: + suffix = "B"; + break; + case 4: + suffix = "T"; + break; + case 5: + suffix = "q"; + break; + default: + return "?"; } const digits = ((ret.length + 2) % 3) + 1; let decimalNumber = ret.slice(digits, digits + 2); @@ -491,18 +487,18 @@ export function verifyLang(lang?: string): boolean { } switch (lang) { - case "es": - case "fr": - case "de": - case "it": - case "zh-CN": - case "zh-TW": - case "pt-BR": - case "ko": - case "ja": - return true; - default: - return false; + case "es": + case "fr": + case "de": + case "it": + case "zh-CN": + case "zh-TW": + case "pt-BR": + case "ko": + case "ja": + return true; + default: + return false; } }