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/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/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/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/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/src/battle-scene.ts b/src/battle-scene.ts index 850d0baab5d..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; @@ -2363,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 @@ -2425,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); } @@ -2482,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)) { @@ -3052,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++; @@ -3122,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; @@ -3174,6 +3193,9 @@ export default class BattleScene extends SceneBase { 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; diff --git a/src/data/ability.ts b/src/data/ability.ts index 07fd48e2f91..ebdd5105bb4 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2579,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) { @@ -4079,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) { @@ -4200,6 +4200,11 @@ export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr { export class BlockRedirectAbAttr extends AbAttr { } +/** + * Used by Early Bird, makes the pokemon wake up faster + * @param statusEffect - The {@linkcode StatusEffect} to check for + * @see {@linkcode apply} + */ export class ReduceStatusEffectDurationAbAttr extends AbAttr { private statusEffect: StatusEffect; @@ -4209,9 +4214,19 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr { this.statusEffect = statusEffect; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + /** + * Reduces the number of sleep turns remaining by an extra 1 when applied + * @param args - The args passed to the `AbAttr`: + * - `[0]` - The {@linkcode StatusEffect} of the Pokemon + * - `[1]` - The number of turns remaining until the status is healed + * @returns `true` if the ability was applied + */ + apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (!(args[1] instanceof Utils.NumberHolder)) { + return false; + } if (args[0] === this.statusEffect) { - (args[1] as Utils.IntegerHolder).value = Utils.toDmgValue((args[1] as Utils.IntegerHolder).value / 2); + args[1].value -= 1; return true; } @@ -4342,6 +4357,30 @@ export class AlwaysHitAbAttr extends AbAttr { } /** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */ export class IgnoreProtectOnContactAbAttr extends AbAttr { } +/** + * Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(Ability) | Infiltrator}. + * Allows the source's moves to bypass the effects of opposing Light Screen, Reflect, Aurora Veil, Safeguard, Mist, and Substitute. + */ +export class InfiltratorAbAttr extends AbAttr { + /** + * Sets a flag to bypass screens, Substitute, Safeguard, and Mist + * @param pokemon n/a + * @param passive n/a + * @param simulated n/a + * @param cancelled n/a + * @param args `[0]` a {@linkcode Utils.BooleanHolder | BooleanHolder} containing the flag + * @returns `true` if the bypass flag was successfully set; `false` otherwise. + */ + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): boolean { + const bypassed = args[0]; + if (args[0] instanceof Utils.BooleanHolder) { + bypassed.value = true; + return true; + } + return false; + } +} + export class UncopiableAbilityAbAttr extends AbAttr { constructor() { super(false); @@ -4913,8 +4952,7 @@ export function initAbilities() { .attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1) .ignorable(), new Ability(Abilities.SHIELD_DUST, 3) - .attr(IgnoreMoveEffectsAbAttr) - .edgeCase(), // Does not work with secret power (unimplemented) + .attr(IgnoreMoveEffectsAbAttr), new Ability(Abilities.OWN_TEMPO, 3) .attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED) .attr(IntimidateImmunityAbAttr) @@ -4958,8 +4996,7 @@ export function initAbilities() { .attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPATK, 1) .ignorable(), new Ability(Abilities.SERENE_GRACE, 3) - .attr(MoveEffectChanceMultiplierAbAttr, 2) - .edgeCase(), // does not work with secret power (unimplemented) + .attr(MoveEffectChanceMultiplierAbAttr, 2), new Ability(Abilities.SWIFT_SWIM, 3) .attr(StatMultiplierAbAttr, Stat.SPD, 2) .condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)), @@ -5323,7 +5360,8 @@ export function initAbilities() { .attr(PostSummonTransformAbAttr) .attr(UncopiableAbilityAbAttr), new Ability(Abilities.INFILTRATOR, 5) - .unimplemented(), + .attr(InfiltratorAbAttr) + .partial(), // does not bypass Mist new Ability(Abilities.MUMMY, 5) .attr(PostDefendAbilityGiveAbAttr, Abilities.MUMMY) .bypassFaint(), @@ -5535,16 +5573,18 @@ 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(), + .bypassFaint(), new Ability(Abilities.CORROSION, 7) .attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ]) .edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented), fling with toxic orb (not implemented yet), and synchronize (not fully implemented yet) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 71cf11fa06f..aa6aec6f73a 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1,13 +1,13 @@ 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, 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"; +import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, InfiltratorAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; import { Stat } from "#enums/stat"; import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; import i18next from "i18next"; @@ -36,7 +36,7 @@ export abstract class ArenaTag { public side: ArenaTagSide = ArenaTagSide.BOTH ) {} - apply(arena: Arena, args: any[]): boolean { + apply(arena: Arena, simulated: boolean, ...args: unknown[]): boolean { return true; } @@ -88,13 +88,13 @@ export abstract class ArenaTag { */ 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) ?? []; + case ArenaTagSide.PLAYER: + return scene.getPlayerField() ?? []; + case ArenaTagSide.ENEMY: + return scene.getEnemyField() ?? []; + case ArenaTagSide.BOTH: + default: + return scene.getField(true) ?? []; } } } @@ -122,10 +122,31 @@ 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, attacker: Pokemon, cancelled: BooleanHolder): boolean { + // `StatStageChangePhase` currently doesn't have a reference to the source of stat drops, + // so this code currently has no effect on gameplay. + if (attacker) { + const bypassed = new BooleanHolder(false); + // TODO: Allow this to be simulated + applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); + if (bypassed.value) { + return false; + } + } - arena.scene.queueMessage(i18next.t("arenaTag:mistApply")); + cancelled.value = true; + + if (!simulated) { + arena.scene.queueMessage(i18next.t("arenaTag:mistApply")); + } return true; } @@ -157,17 +178,21 @@ 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 attacker the attacking {@linkcode Pokemon} + * @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, attacker: Pokemon, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean { + if (this.weakenedCategories.includes(moveCategory)) { + const bypassed = new BooleanHolder(false); + applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); + if (bypassed.value) { + return false; + } + damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5; return true; } return false; @@ -249,38 +274,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; } @@ -296,7 +317,7 @@ export class ConditionalProtectTag extends ArenaTag { */ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => { const move = allMoves[moveId]; - const priority = new Utils.NumberHolder(move.priority); + const priority = new NumberHolder(move.priority); const effectPhase = arena.scene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase) { @@ -328,11 +349,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; }; @@ -460,7 +481,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"); } @@ -497,12 +518,19 @@ export class WeakenMoveTypeTag extends ArenaTag { 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; } } @@ -563,13 +591,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; } @@ -608,16 +635,22 @@ 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; } @@ -651,14 +684,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); @@ -702,8 +739,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)) { @@ -784,31 +824,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) { @@ -818,12 +858,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; @@ -853,14 +897,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; } } @@ -879,8 +929,15 @@ export class TrickRoomTag extends ArenaTag { 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; } @@ -1087,7 +1144,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); @@ -1111,8 +1168,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; } @@ -1138,63 +1202,63 @@ class GrassWaterPledgeTag extends ArenaTag { // 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; } } 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/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 a5016746013..c3b7765d062 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1,29 +1,44 @@ -import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims"; -import { getPokemonNameWithAffix } from "../messages"; -import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; -import { StatusEffect } from "./status-effect"; -import * as Utils from "../utils"; -import { ChargeAttr, MoveFlags, allMoves, MoveCategory, applyMoveAttrs, StatusCategoryOnAllyAttr, HealOnAllyAttr, ConsecutiveUseDoublePowerAttr } from "./move"; -import { Type } from "./type"; -import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability"; -import { TerrainType } from "./terrain"; -import { WeatherType } from "./weather"; -import { allAbilities } from "./ability"; -import { SpeciesFormChangeManualTrigger } from "./pokemon-forms"; -import { Abilities } from "#enums/abilities"; -import { BattlerTagType } from "#enums/battler-tag-type"; -import { Moves } from "#enums/moves"; -import { Species } from "#enums/species"; -import i18next from "#app/plugins/i18n"; -import { Stat, type BattleStat, type EffectiveStat, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat"; +import BattleScene from "#app/battle-scene"; +import { + allAbilities, + applyAbAttrs, + BlockNonDirectDamageAbAttr, + FlinchEffectAbAttr, + ProtectStatAbAttr, + ReverseDrainAbAttr +} from "#app/data/ability"; +import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims"; +import Move, { + allMoves, + applyMoveAttrs, + ChargeAttr, + ConsecutiveUseDoublePowerAttr, + HealOnAllyAttr, + MoveCategory, + MoveFlags, + StatusCategoryOnAllyAttr +} from "#app/data/move"; +import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms"; +import { StatusEffect } from "#app/data/status-effect"; +import { TerrainType } from "#app/data/terrain"; +import { Type } from "#app/data/type"; +import { WeatherType } from "#app/data/weather"; +import Pokemon, { HitResult, MoveResult } from "#app/field/pokemon"; +import { getPokemonNameWithAffix } from "#app/messages"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MovePhase } from "#app/phases/move-phase"; 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"; +import { StatStageChangeCallback, StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; +import i18next from "#app/plugins/i18n"; +import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Moves } from "#enums/moves"; +import { PokemonAnimType } from "#enums/pokemon-anim-type"; +import { Species } from "#enums/species"; +import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat } from "#enums/stat"; export enum BattlerTagLapseType { FAINT, @@ -33,6 +48,7 @@ export enum BattlerTagLapseType { MOVE_EFFECT, TURN_END, HIT, + AFTER_HIT, CUSTOM } @@ -405,7 +421,7 @@ export class RechargingTag extends BattlerTag { */ export class BeakBlastChargingTag extends BattlerTag { constructor() { - super(BattlerTagType.BEAK_BLAST_CHARGING, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 1, Moves.BEAK_BLAST); + super(BattlerTagType.BEAK_BLAST_CHARGING, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END, BattlerTagLapseType.AFTER_HIT ], 1, Moves.BEAK_BLAST); } onAdd(pokemon: Pokemon): void { @@ -421,16 +437,13 @@ export class BeakBlastChargingTag extends BattlerTag { * to be removed after the source makes a move (or the turn ends, whichever comes first) * @param pokemon {@linkcode Pokemon} the owner of this tag * @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle - * @returns `true` if invoked with the CUSTOM lapse type; `false` otherwise + * @returns `true` if invoked with the `AFTER_HIT` lapse type */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { - if (lapseType === BattlerTagLapseType.CUSTOM) { - const effectPhase = pokemon.scene.getCurrentPhase(); - if (effectPhase instanceof MoveEffectPhase) { - const attacker = effectPhase.getPokemon(); - if (effectPhase.move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { - attacker.trySetStatus(StatusEffect.BURN, true, pokemon); - } + if (lapseType === BattlerTagLapseType.AFTER_HIT) { + const phaseData = getMoveEffectPhaseData(pokemon); + if (phaseData?.move.hasFlag(MoveFlags.MAKES_CONTACT)) { + phaseData.attacker.trySetStatus(StatusEffect.BURN, true, pokemon); } return true; } @@ -444,11 +457,10 @@ export class BeakBlastChargingTag extends BattlerTag { * @see {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Trap_(move) | Shell Trap} */ export class ShellTrapTag extends BattlerTag { - public activated: boolean; + public activated: boolean = false; constructor() { - super(BattlerTagType.SHELL_TRAP, BattlerTagLapseType.TURN_END, 1); - this.activated = false; + super(BattlerTagType.SHELL_TRAP, [ BattlerTagLapseType.TURN_END, BattlerTagLapseType.AFTER_HIT ], 1); } onAdd(pokemon: Pokemon): void { @@ -459,25 +471,33 @@ export class ShellTrapTag extends BattlerTag { * "Activates" the shell trap, causing the tag owner to move next. * @param pokemon {@linkcode Pokemon} the owner of this tag * @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle - * @returns `true` if invoked with the `CUSTOM` lapse type; `false` otherwise + * @returns `true` if invoked with the `AFTER_HIT` lapse type */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { - if (lapseType === BattlerTagLapseType.CUSTOM) { - const shellTrapPhaseIndex = pokemon.scene.phaseQueue.findIndex( - phase => phase instanceof MovePhase && phase.pokemon === pokemon - ); - const firstMovePhaseIndex = pokemon.scene.phaseQueue.findIndex( - phase => phase instanceof MovePhase - ); + if (lapseType === BattlerTagLapseType.AFTER_HIT) { + const phaseData = getMoveEffectPhaseData(pokemon); - if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) { - const shellTrapMovePhase = pokemon.scene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0]; - pokemon.scene.prependToPhase(shellTrapMovePhase, MovePhase); + // Trap should only be triggered by opponent's Physical moves + if (phaseData?.move.category === MoveCategory.PHYSICAL && pokemon.isOpponent(phaseData.attacker)) { + const shellTrapPhaseIndex = pokemon.scene.phaseQueue.findIndex( + phase => phase instanceof MovePhase && phase.pokemon === pokemon + ); + const firstMovePhaseIndex = pokemon.scene.phaseQueue.findIndex( + phase => phase instanceof MovePhase + ); + + // Only shift MovePhase timing if it's not already next up + if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) { + const shellTrapMovePhase = pokemon.scene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0]; + pokemon.scene.prependToPhase(shellTrapMovePhase, MovePhase); + } + + this.activated = true; } - this.activated = true; return true; } + return super.lapse(pokemon, lapseType); } } @@ -641,7 +661,7 @@ export class ConfusedTag extends BattlerTag { if (pokemon.randSeedInt(3) === 0) { const atk = pokemon.getEffectiveStat(Stat.ATK); const def = pokemon.getEffectiveStat(Stat.DEF); - const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100)); + const damage = toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100)); pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); pokemon.damageAndUpdate(damage); pokemon.battleData.hitCount++; @@ -812,13 +832,13 @@ export class SeedTag extends BattlerTag { if (ret) { const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex); if (source) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); - const damage = pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8)); + const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8)); const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), !reverseDrain ? damage : damage * -1, @@ -838,7 +858,7 @@ export class SeedTag extends BattlerTag { export class NightmareTag extends BattlerTag { constructor() { - super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.AFTER_MOVE, 1, Moves.NIGHTMARE); + super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.TURN_END, 1, Moves.NIGHTMARE); } onAdd(pokemon: Pokemon): void { @@ -860,11 +880,11 @@ export class NightmareTag extends BattlerTag { pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4)); + pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4)); } } @@ -919,14 +939,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) { @@ -1004,7 +1024,7 @@ export class IngrainTag extends TrappedTag { new PokemonHealPhase( pokemon.scene, pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 16), + toDmgValue(pokemon.getMaxHp() / 16), i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), true ) @@ -1067,7 +1087,7 @@ export class AquaRingTag extends BattlerTag { new PokemonHealPhase( pokemon.scene, pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 16), + toDmgValue(pokemon.getMaxHp() / 16), i18next.t("battlerTags:aquaRingLapse", { moveName: this.getMoveName(), pokemonName: getPokemonNameWithAffix(pokemon) @@ -1161,11 +1181,11 @@ export abstract class DamagingTrapTag extends TrappedTag { ); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim)); - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8)); + pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8)); } } @@ -1356,7 +1376,7 @@ export class ContactDamageProtectedTag extends ProtectedTag { if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { - attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); + attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); } } } @@ -1641,12 +1661,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); @@ -1709,7 +1729,7 @@ export class SemiInvulnerableTag extends BattlerTag { onRemove(pokemon: Pokemon): void { // Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible pokemon.scene.tweens.addCounter({ - duration: Utils.getFrameMs(2), + duration: getFrameMs(2), onComplete: () => pokemon.setVisible(true) }); } @@ -1860,12 +1880,12 @@ export class SaltCuredTag extends BattlerTag { if (ret) { pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER); - pokemon.damageAndUpdate(Utils.toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8)); + pokemon.damageAndUpdate(toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8)); pokemon.scene.queueMessage( i18next.t("battlerTags:saltCuredLapse", { @@ -1907,11 +1927,11 @@ export class CursedTag extends BattlerTag { if (ret) { pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4)); + pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4)); pokemon.scene.queueMessage(i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } @@ -2173,7 +2193,7 @@ export class GulpMissileTag extends BattlerTag { return true; } - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); if (!cancelled.value) { @@ -2289,7 +2309,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { * @returns `true` if the move cannot be used because the target is an ally */ override isMoveTargetRestricted(move: Moves, user: Pokemon, target: Pokemon) { - const moveCategory = new Utils.IntegerHolder(allMoves[move].category); + const moveCategory = new NumberHolder(allMoves[move].category); applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory); if (allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS ) { return true; @@ -2436,15 +2456,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 } @@ -2506,7 +2526,7 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { const ret = super.lapse(pokemon, lapseType); if (lapseType === BattlerTagLapseType.CUSTOM) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); if (!cancelled.value) { if (pokemon.mysteryEncounterBattleEffects) { @@ -2724,6 +2744,44 @@ export class TelekinesisTag extends BattlerTag { } } +/** + * 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 sourceId - The ID of the pokemon adding the tag @@ -2731,177 +2789,179 @@ export class TelekinesisTag extends BattlerTag { */ 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.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.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); } } @@ -2915,3 +2975,22 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag { tag.loadTag(source); return tag; } + +/** + * Helper function to verify that the current phase is a MoveEffectPhase and provide quick access to commonly used fields + * + * @param pokemon {@linkcode Pokemon} The Pokémon used to access the current phase + * @returns null if current phase is not MoveEffectPhase, otherwise Object containing the {@linkcode MoveEffectPhase}, and its + * corresponding {@linkcode Move} and user {@linkcode Pokemon} + */ +function getMoveEffectPhaseData(pokemon: Pokemon): {phase: MoveEffectPhase, attacker: Pokemon, move: Move} | null { + const phase = pokemon.scene.getCurrentPhase(); + if (phase instanceof MoveEffectPhase) { + return { + phase : phase, + attacker : phase.getPokemon(), + move : phase.move.getMove() + }; + } + return null; +} 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 c475fc729e6..0f88c53b748 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -262,14 +262,14 @@ export class Egg { return "Manaphy"; } switch (this.tier) { - 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"); + 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"); } } @@ -288,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 ""; } } @@ -315,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); @@ -335,12 +335,12 @@ export class Egg { } switch (eggTier ?? this._tier) { - case EggTier.COMMON: - return HATCH_WAVES_COMMON_EGG; - case EggTier.RARE: - return HATCH_WAVES_RARE_EGG; - case EggTier.EPIC: - 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; } @@ -379,22 +379,22 @@ export class Egg { let maxStarterValue: integer; switch (this.tier) { - 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; + 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 ]; @@ -469,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); @@ -523,15 +523,15 @@ export class Egg { return; } switch (this.tier) { - case EggTier.RARE: - scene.gameData.gameStats.rareEggsPulled++; - break; - case EggTier.EPIC: - scene.gameData.gameStats.epicEggsPulled++; - break; - case EggTier.LEGENDARY: - 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; } } 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 a77e8096672..ec25844909e 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -8,7 +8,7 @@ import { Constructor, NumberHolder } from "#app/utils"; import * as Utils from "../utils"; import { WeatherType } from "./weather"; import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag"; -import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability"; +import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability"; import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier"; import { BattlerIndex, BattleType } from "../battle"; import { TerrainType } from "./terrain"; @@ -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; } @@ -346,7 +346,11 @@ export default class Move implements Localizable { return false; } - return !user.hasAbility(Abilities.INFILTRATOR) + const bypassed = new Utils.BooleanHolder(false); + // TODO: Allow this to be simulated + applyAbAttrs(InfiltratorAbAttr, user, null, false, bypassed); + + return !bypassed.value && !this.hasFlag(MoveFlags.SOUND_BASED) && !this.hasFlag(MoveFlags.IGNORE_SUBSTITUTE); } @@ -616,26 +620,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); @@ -808,7 +812,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); } @@ -1024,9 +1028,9 @@ export class MoveEffectAttr extends MoveAttr { 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) { @@ -1745,17 +1749,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; } } } @@ -1763,10 +1767,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; } } } @@ -1996,33 +2000,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); } } } @@ -2046,15 +2050,15 @@ export class WaterShurikenMultiHitTypeAttr extends ChangeMultiHitTypeAttr { export class StatusEffectAttr extends MoveEffectAttr { public effect: StatusEffect; - public cureTurn: integer | null; - public overrideStatus: boolean; + public turnsRemaining?: number; + public overrideStatus: boolean = false; - constructor(effect: StatusEffect, selfTarget?: boolean, cureTurn?: integer, overrideStatus?: boolean) { + constructor(effect: StatusEffect, selfTarget?: boolean, turnsRemaining?: number, overrideStatus: boolean = false) { super(selfTarget, MoveEffectTrigger.HIT); this.effect = effect; - this.cureTurn = cureTurn!; // TODO: is this bang correct? - this.overrideStatus = !!overrideStatus; + this.turnsRemaining = turnsRemaining; + this.overrideStatus = overrideStatus; } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { @@ -2074,14 +2078,14 @@ export class StatusEffectAttr extends MoveEffectAttr { } } - if (user !== target && target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)) { + if (user !== target && target.isSafeguarded(user)) { if (move.category === MoveCategory.STATUS) { user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) })); } return false; } if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0)) - && pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) { + && pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining)) { applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect); return true; } @@ -2098,8 +2102,8 @@ export class StatusEffectAttr extends MoveEffectAttr { export class MultiStatusEffectAttr extends StatusEffectAttr { public effects: StatusEffect[]; - constructor(effects: StatusEffect[], selfTarget?: boolean, cureTurn?: integer, overrideStatus?: boolean) { - super(effects[0], selfTarget, cureTurn, overrideStatus); + constructor(effects: StatusEffect[], selfTarget?: boolean, turnsRemaining?: number, overrideStatus?: boolean) { + super(effects[0], selfTarget, turnsRemaining, overrideStatus); this.effects = effects; } @@ -2845,26 +2849,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; @@ -2875,6 +2879,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; @@ -3153,21 +3313,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; } @@ -3387,24 +3547,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; @@ -3424,21 +3584,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; @@ -3554,13 +3714,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; } } @@ -3834,8 +3994,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 { @@ -3957,14 +4117,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; } } @@ -3983,10 +4143,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; } } @@ -4216,21 +4376,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; } @@ -4250,12 +4410,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; } @@ -4275,15 +4435,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; } @@ -4303,22 +4463,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; } @@ -4336,23 +4496,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; } @@ -4387,20 +4547,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; } @@ -4500,26 +4660,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; } } } @@ -4736,7 +4896,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; @@ -4764,48 +4924,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; } } @@ -5005,7 +5165,7 @@ export class ConfuseAttr extends AddBattlerTagAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!this.selfTarget && target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)) { + if (!this.selfTarget && target.isSafeguarded(user)) { if (move.category === MoveCategory.STATUS) { user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) })); } @@ -5174,7 +5334,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; } @@ -5249,7 +5409,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; @@ -5386,7 +5546,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; } @@ -5778,19 +5938,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? @@ -5834,131 +5994,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 }); @@ -6430,6 +6590,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); @@ -6993,47 +7156,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 }; @@ -7439,6 +7602,7 @@ export function initMoves() { .ignoresVirtual(), new StatusMove(Moves.TRANSFORM, Type.NORMAL, -1, 10, -1, 0, 1) .attr(TransformAttr) + .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE)) .ignoresProtect(), new AttackMove(Moves.BUBBLE, Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1) .attr(StatStageChangeAttr, [ Stat.SPD ], -1) @@ -7869,7 +8033,7 @@ export function initMoves() { .attr(RemoveScreensAttr), new StatusMove(Moves.YAWN, Type.NORMAL, -1, 10, -1, 0, 3) .attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true) - .condition((user, target, move) => !target.status && !target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)), + .condition((user, target, move) => !target.status && !target.isSafeguarded(user)), new AttackMove(Moves.KNOCK_OFF, Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3) .attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1) .attr(RemoveHeldItemAttr, false), @@ -7895,7 +8059,7 @@ export function initMoves() { .unimplemented(), new AttackMove(Moves.SECRET_POWER, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, 30, 0, 3) .makesContact(false) - .partial(), // No effect implemented + .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) @@ -8153,7 +8317,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) 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 4f3420f5194..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 @@ -154,7 +154,7 @@ export const ATrainersTestEncounter: MysteryEncounter = }; 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( diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 70b2d50fe99..c53b802bb22 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -286,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() @@ -328,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() @@ -359,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 ab892ae00f2..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 @@ -133,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/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index b5d47cf6912..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"; @@ -356,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 be52ab42c9d..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 ] }, @@ -353,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; } } }) @@ -426,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 0f784739777..55d7ce0e92d 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -228,7 +228,7 @@ export const DancingLessonsEncounter: MysteryEncounter = }) .withOptionPhase(async (scene: BattleScene) => { // Learn its Dance - hideOricorioPokemon(scene); + await hideOricorioPokemon(scene); leaveEncounterWithoutBattle(scene, true); }) .build() @@ -236,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`, @@ -303,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 5ad6630386f..7f199b5487c 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -182,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/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index d44e7bae596..d306206159a 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -222,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 380662ca817..889868519d2 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -145,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 549faa01fa1..b843a929c08 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -152,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 bafc1901e5e..376bdf0c95d 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -399,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 8fd46982dc1..8e7ea52a967 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -129,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 fb25976ebd8..f282064bb94 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -147,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( @@ -172,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( @@ -200,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 1eb1c4cb13e..693d935ae17 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -184,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 17a3a366569..092d2ab2673 100644 --- a/src/data/mystery-encounters/encounters/part-timer-encounter.ts +++ b/src/data/mystery-encounters/encounters/part-timer-encounter.ts @@ -227,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 c6b04b7aca6..0ee3c57b0a2 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -303,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 c70048ade07..d30c97b27de 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -138,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() ) @@ -204,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 3a4bf465a78..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 = { @@ -143,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 01e241f63d4..e8f11f02e18 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -149,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 0ac82243862..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; @@ -221,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); @@ -245,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() ) @@ -273,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); @@ -297,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() ) @@ -325,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); @@ -349,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() ) @@ -521,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) { @@ -609,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-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index 7ee57d36027..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: [ @@ -201,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 c7cb23fe6f8..bf322802f81 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -111,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 10bb956636b..9f80bbbffde 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -162,7 +162,7 @@ export const TrainingSessionEncounter: MysteryEncounter = setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .build() ) @@ -238,7 +238,7 @@ export const TrainingSessionEncounter: MysteryEncounter = setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .build() ) @@ -351,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 c2a0426bceb..2b3b38b2164 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -105,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 ]); @@ -136,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!; @@ -222,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 13594f273d9..b7ce3bab48c 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -210,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 6e2f8352480..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"; @@ -379,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 7e175957e21..ee9eb159e10 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -325,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; } 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 8bb23cfc208..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; } } @@ -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), diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index ffe32a02aeb..56e754ac407 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -1,4 +1,4 @@ -import * as Utils from "../utils"; +import { randIntRange } from "#app/utils"; import { StatusEffect } from "#enums/status-effect"; import i18next, { ParseKeys } from "i18next"; @@ -6,17 +6,21 @@ export { StatusEffect }; export class Status { public effect: StatusEffect; - public turnCount: integer; - public cureTurn: integer | null; + /** Toxic damage is `1/16 max HP * toxicTurnCount` */ + public toxicTurnCount: number = 0; + public sleepTurnsRemaining?: number; - constructor(effect: StatusEffect, turnCount: integer = 0, cureTurn?: integer) { + constructor(effect: StatusEffect, toxicTurnCount: number = 0, sleepTurnsRemaining?: number) { this.effect = effect; - this.turnCount = turnCount === undefined ? 0 : turnCount; - this.cureTurn = cureTurn!; // TODO: is this bang correct? + this.toxicTurnCount = toxicTurnCount; + this.sleepTurnsRemaining = sleepTurnsRemaining; } incrementTurn(): void { - this.turnCount++; + this.toxicTurnCount++; + if (this.sleepTurnsRemaining) { + this.sleepTurnsRemaining--; + } } isPostTurn(): boolean { @@ -26,20 +30,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 +94,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; @@ -107,7 +111,7 @@ export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect): * Returns a random non-volatile StatusEffect */ export function generateRandomStatusEffect(): StatusEffect { - return Utils.randIntRange(1, 6); + return randIntRange(1, 6); } /** @@ -123,7 +127,7 @@ export function getRandomStatusEffect(statusEffectA: StatusEffect, statusEffectB return statusEffectA; } - return Utils.randIntRange(0, 2) ? statusEffectA : statusEffectB; + return randIntRange(0, 2) ? statusEffectA : statusEffectB; } /** @@ -140,7 +144,7 @@ export function getRandomStatus(statusA: Status | null, statusB: Status | null): } - return Utils.randIntRange(0, 2) ? statusA : statusB; + return randIntRange(0, 2) ? statusA : statusB; } /** 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 2efae9ad359..680dedb93cc 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -80,6 +80,7 @@ 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", 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 9ae83753e62..a3d7429ed9b 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,10 +19,10 @@ 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"; +import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, 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, InfiltratorAbAttr } from "#app/data/ability"; import PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#app/battle"; import { Mode } from "#app/ui/ui"; @@ -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 ]; } } @@ -917,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; @@ -1023,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 { @@ -1198,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]) { @@ -1218,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); @@ -1230,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]) { @@ -1262,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; } @@ -1288,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) { @@ -1318,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; @@ -1538,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; } @@ -2018,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(); @@ -2187,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 { @@ -2285,6 +2290,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate); } + /** + * Compares if `this` and {@linkcode target} are on the same team. + * @param target the {@linkcode Pokemon} to compare against. + * @returns `true` if the two pokemon are allies, `false` otherwise + */ + public isOpponent(target: Pokemon): boolean { + return this.isPlayer() !== target.isPlayer(); + } + getOpponent(targetIndex: integer): Pokemon | null { const ret = this.getOpponents()[targetIndex]; if (ret.summonData) { @@ -2338,14 +2352,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) { @@ -2605,7 +2619,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, source, moveCategory, screenMultiplier); /** * For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if: @@ -2795,30 +2809,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; } } @@ -3048,6 +3063,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { continue; } + if (tag instanceof PowerTrickTag) { + tag.swapStat(this); + } + this.summonData.tags.push(tag); } @@ -3342,61 +3361,60 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - const types = this.getTypes(true, true); - - const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (sourcePokemon && sourcePokemon !== this && this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { + if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) { return false; } - switch (effect) { - 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 => { - // Check if the Pokemon is not immune to Poison/Toxic - if (defType !== Type.POISON && defType !== Type.STEEL) { - return false; - } + const types = this.getTypes(true, true); - // 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) { + switch (effect) { + 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 => { + // Check if the Pokemon is not immune to Poison/Toxic + 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) { + 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); @@ -3412,7 +3430,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return true; } - trySetStatus(effect: StatusEffect | undefined, asPhase: boolean = false, sourcePokemon: Pokemon | null = null, cureTurn: integer | null = 0, sourceText: string | null = null): boolean { + trySetStatus(effect?: StatusEffect, asPhase: boolean = false, sourcePokemon: Pokemon | null = null, turnsRemaining: number = 0, sourceText: string | null = null): boolean { if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) { return false; } @@ -3426,15 +3444,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (asPhase) { - this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText, sourcePokemon)); + this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon)); return true; } - let statusCureTurn: Utils.IntegerHolder; + let sleepTurnsRemaining: Utils.NumberHolder; if (effect === StatusEffect.SLEEP) { - statusCureTurn = new Utils.IntegerHolder(this.randSeedIntRange(2, 4)); - applyAbAttrs(ReduceStatusEffectDurationAbAttr, this, null, false, effect, statusCureTurn); + sleepTurnsRemaining = new Utils.NumberHolder(this.randSeedIntRange(2, 4)); this.setFrameRate(4); @@ -3454,9 +3471,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - statusCureTurn = statusCureTurn!; // tell TS compiler it's defined + sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call - this.status = new Status(effect, 0, statusCureTurn?.value); + this.status = new Status(effect, 0, sleepTurnsRemaining?.value); if (effect !== StatusEffect.FAINT) { this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true); @@ -3494,6 +3511,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } + /** + * Checks if this Pokemon is protected by Safeguard + * @param attacker the {@linkcode Pokemon} inflicting status on this Pokemon + * @returns `true` if this Pokemon is protected by Safeguard; `false` otherwise. + */ + isSafeguarded(attacker: Pokemon): boolean { + const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + if (this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { + const bypassed = new Utils.BooleanHolder(false); + if (attacker) { + applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); + } + return !bypassed.value; + } + return false; + } + primeSummonData(summonDataPrimer: PokemonSummonData): void { this.summonDataPrimer = summonDataPrimer; } @@ -3966,16 +4000,20 @@ export class PlayerPokemon extends Pokemon { super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); if (Overrides.STATUS_OVERRIDE) { - this.status = new Status(Overrides.STATUS_OVERRIDE); + this.status = new Status(Overrides.STATUS_OVERRIDE, 0, 4); } 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(); @@ -4077,7 +4115,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); @@ -4287,12 +4325,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 = () => { @@ -4329,7 +4388,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; } @@ -4421,7 +4480,7 @@ export class EnemyPokemon extends Pokemon { } if (Overrides.OPP_STATUS_OVERRIDE) { - this.status = new Status(Overrides.OPP_STATUS_OVERRIDE); + this.status = new Status(Overrides.OPP_STATUS_OVERRIDE, 0, 4); } if (Overrides.OPP_GENDER_OVERRIDE) { @@ -4430,9 +4489,11 @@ export class EnemyPokemon extends Pokemon { const speciesId = this.species.speciesId; - if (speciesId in Overrides.OPP_FORM_OVERRIDES + if ( + speciesId in Overrides.OPP_FORM_OVERRIDES && Overrides.OPP_FORM_OVERRIDES[speciesId] - && this.species.forms[Overrides.OPP_FORM_OVERRIDES[speciesId]]) { + && this.species.forms[Overrides.OPP_FORM_OVERRIDES[speciesId]] + ) { this.formIndex = Overrides.OPP_FORM_OVERRIDES[speciesId] ?? 0; } @@ -4443,10 +4504,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; } } @@ -4496,35 +4560,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; } } @@ -4564,135 +4628,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] }; } } @@ -4840,10 +4904,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); diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 5110787452f..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; @@ -515,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 dd8c82357a7..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; diff --git a/src/overrides.ts b/src/overrides.ts index 211d430a835..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> = {}; /** diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index cf66631bd96..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 = + // 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/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 581cd5ff017..8d1a255d268 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -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 @@ -278,10 +280,8 @@ export class MoveEffectPhase extends PokemonPhase { if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); } - target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING); - if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) { - target.lapseTag(BattlerTagType.SHELL_TRAP); - } + target.lapseTags(BattlerTagLapseType.AFTER_HIT); + })).then(() => { // Apply the user's post-attack ability effects applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => { diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 94093188571..e9d8887e9cb 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import BattleScene from "#app/battle-scene"; -import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr } from "#app/data/ability"; +import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr, ReduceStatusEffectDurationAbAttr } from "#app/data/ability"; import { CommonAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags"; import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, ChargeAttr, CopyMoveAttr, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move"; @@ -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(); @@ -145,8 +147,9 @@ export class MovePhase extends BattlePhase { 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(); } } @@ -164,23 +167,26 @@ export class MovePhase extends BattlePhase { 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()); + const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0); + applyAbAttrs(ReduceStatusEffectDurationAbAttr, this.pokemon, null, false, this.pokemon.status.effect, turnsRemaining); + this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value; + healed = this.pokemon.status.sleepTurnsRemaining <= 0; + 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) { 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 c396fa7ba59..01384b932cb 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -8,26 +8,26 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonPhase } from "./pokemon-phase"; export class ObtainStatusEffectPhase extends PokemonPhase { - private statusEffect?: StatusEffect | undefined; - private cureTurn?: integer | null; + private statusEffect?: StatusEffect; + private turnsRemaining?: number; private sourceText?: string | null; private sourcePokemon?: Pokemon | null; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string | null, sourcePokemon?: Pokemon | null) { + constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, turnsRemaining?: number, sourceText?: string | null, sourcePokemon?: Pokemon | null) { super(scene, battlerIndex); this.statusEffect = statusEffect; - this.cureTurn = cureTurn; + this.turnsRemaining = turnsRemaining; this.sourceText = sourceText; - this.sourcePokemon = sourcePokemon; // For tracking which Pokemon caused the status effect + this.sourcePokemon = sourcePokemon; } start() { const pokemon = this.getPokemon(); if (pokemon && !pokemon.status) { if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) { - if (this.cureTurn) { - pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct? + if (this.turnsRemaining) { + pokemon.status!.sleepTurnsRemaining = this.turnsRemaining; } pokemon.updateInfo(true); new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => { 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..3db98d9926c 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -18,9 +18,9 @@ export class PostSummonPhase extends PokemonPhase { const pokemon = this.getPokemon(); if (pokemon.status?.effect === StatusEffect.TOXIC) { - pokemon.status.turnCount = 0; + pokemon.status.toxicTurnCount = 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..2efd992a2b5 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.toxicTurnCount), 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 bfe19ea9ca5..2d4b3ce6c6f 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -64,8 +64,8 @@ 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); + // TODO: add a reference to the source of the stat change to fix Infiltrator interaction + this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, null, false, cancelled); } if (!cancelled.value && !this.selfTarget && stages.value < 0) { diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 627cee4b06a..497d449912f 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -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,55 +152,55 @@ 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 && 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 { - 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; } } diff --git a/src/system/achv.ts b/src/system/achv.ts index 7329dd41fd5..366813328e2 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -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/game-data.ts b/src/system/game-data.ts index b162962fac6..5f9aad63408 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -43,10 +43,9 @@ 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"; @@ -68,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"; } } @@ -403,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)); } @@ -482,7 +478,7 @@ export class GameData { localStorage.setItem(lsItemKey, ""); } - applySystemDataPatches(systemData); + applySystemVersionMigration(systemData); this.trainerId = systemData.trainerId; this.secretId = systemData.secretId; @@ -857,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]); @@ -1313,7 +1309,7 @@ export class GameData { return v; }) as SessionSaveData; - applySessionDataPatches(sessionData); + applySessionVersionMigration(sessionData); return sessionData; } @@ -1354,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)); } @@ -1381,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" }); @@ -1441,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..e681c995b26 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); @@ -128,7 +137,7 @@ export default class PokemonData { this.moveset = (source.moveset || [ new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL) ]).filter(m => m).map((m: any) => new PokemonMove(m.moveId, m.ppUsed, m.ppUp)); if (!forHistory) { this.status = source.status - ? new Status(source.status.effect, source.status.turnCount, source.status.cureTurn) + ? new Status(source.status.effect, source.status.toxicTurnCount, source.status.sleepTurnsRemaining) : null; } 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/early_bird.test.ts b/src/test/abilities/early_bird.test.ts new file mode 100644 index 00000000000..a69290fa1e4 --- /dev/null +++ b/src/test/abilities/early_bird.test.ts @@ -0,0 +1,93 @@ +import { Status } from "#app/data/status-effect"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Early Bird", () => { + 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.REST, Moves.BELLY_DRUM, Moves.SPLASH ]) + .ability(Abilities.EARLY_BIRD) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("reduces Rest's sleep time to 1 turn", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const player = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.BELLY_DRUM); + await game.toNextTurn(); + game.move.select(Moves.REST); + await game.toNextTurn(); + + expect(player.status?.effect).toBe(StatusEffect.SLEEP); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBe(StatusEffect.SLEEP); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBeUndefined(); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); + + it("reduces 3-turn sleep to 1 turn", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const player = game.scene.getPlayerPokemon()!; + player.status = new Status(StatusEffect.SLEEP, 0, 4); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBe(StatusEffect.SLEEP); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBeUndefined(); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); + + it("reduces 1-turn sleep to 0 turns", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const player = game.scene.getPlayerPokemon()!; + player.status = new Status(StatusEffect.SLEEP, 0, 2); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBeUndefined(); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); +}); diff --git a/src/test/abilities/infiltrator.test.ts b/src/test/abilities/infiltrator.test.ts new file mode 100644 index 00000000000..01c5cef7796 --- /dev/null +++ b/src/test/abilities/infiltrator.test.ts @@ -0,0 +1,107 @@ +import { ArenaTagSide } from "#app/data/arena-tag"; +import { allMoves } from "#app/data/move"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Stat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; +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 - Infiltrator", () => { + 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.TACKLE, Moves.WATER_GUN, Moves.SPORE, Moves.BABY_DOLL_EYES ]) + .ability(Abilities.INFILTRATOR) + .battleType("single") + .disableCrits() + .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .startingLevel(100) + .enemyLevel(100); + }); + + it.each([ + { effectName: "Light Screen", tagType: ArenaTagType.LIGHT_SCREEN, move: Moves.WATER_GUN }, + { effectName: "Reflect", tagType: ArenaTagType.REFLECT, move: Moves.TACKLE }, + { effectName: "Aurora Veil", tagType: ArenaTagType.AURORA_VEIL, move: Moves.TACKLE } + ])("should bypass the target's $effectName", async ({ tagType, move }) => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const player = game.scene.getPlayerPokemon()!; + const enemy = game.scene.getEnemyPokemon()!; + + const preScreenDmg = enemy.getAttackDamage(player, allMoves[move]).damage; + + game.scene.arena.addTag(tagType, 1, Moves.NONE, enemy.id, ArenaTagSide.ENEMY, true); + + const postScreenDmg = enemy.getAttackDamage(player, allMoves[move]).damage; + + expect(postScreenDmg).toBe(preScreenDmg); + expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR); + }); + + it("should bypass the target's Safeguard", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const player = game.scene.getPlayerPokemon()!; + const enemy = game.scene.getEnemyPokemon()!; + + game.scene.arena.addTag(ArenaTagType.SAFEGUARD, 1, Moves.NONE, enemy.id, ArenaTagSide.ENEMY, true); + + game.move.select(Moves.SPORE); + + await game.phaseInterceptor.to("BerryPhase", false); + expect(enemy.status?.effect).toBe(StatusEffect.SLEEP); + expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR); + }); + + // TODO: fix this interaction to pass this test + it.skip("should bypass the target's Mist", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const player = game.scene.getPlayerPokemon()!; + const enemy = game.scene.getEnemyPokemon()!; + + game.scene.arena.addTag(ArenaTagType.MIST, 1, Moves.NONE, enemy.id, ArenaTagSide.ENEMY, true); + + game.move.select(Moves.BABY_DOLL_EYES); + + await game.phaseInterceptor.to("MoveEndPhase"); + expect(enemy.getStatStage(Stat.ATK)).toBe(-1); + expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR); + }); + + it("should bypass the target's Substitute", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const player = game.scene.getPlayerPokemon()!; + const enemy = game.scene.getEnemyPokemon()!; + + enemy.addTag(BattlerTagType.SUBSTITUTE, 1, Moves.NONE, enemy.id); + + game.move.select(Moves.BABY_DOLL_EYES); + + await game.phaseInterceptor.to("MoveEndPhase"); + expect(enemy.getStatStage(Stat.ATK)).toBe(-1); + expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR); + }); +}); diff --git a/src/test/abilities/magic_guard.test.ts b/src/test/abilities/magic_guard.test.ts index 614f983e76e..8075eac66f2 100644 --- a/src/test/abilities/magic_guard.test.ts +++ b/src/test/abilities/magic_guard.test.ts @@ -150,7 +150,7 @@ describe("Abilities - Magic Guard", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; - const toxicStartCounter = enemyPokemon.status!.turnCount; + const toxicStartCounter = enemyPokemon.status!.toxicTurnCount; //should be 0 await game.phaseInterceptor.to(TurnEndPhase); @@ -162,7 +162,7 @@ describe("Abilities - Magic Guard", () => { * - The enemy Pokemon's hypothetical CatchRateMultiplier should be 1.5 */ expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); - expect(enemyPokemon.status!.turnCount).toBeGreaterThan(toxicStartCounter); + expect(enemyPokemon.status!.toxicTurnCount).toBeGreaterThan(toxicStartCounter); expect(getStatusEffectCatchRateMultiplier(enemyPokemon.status!.effect)).toBe(1.5); } ); 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/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/data/status-effect.test.ts b/src/test/data/status-effect.test.ts index bca3bd21c70..8b37da45d8d 100644 --- a/src/test/data/status-effect.test.ts +++ b/src/test/data/status-effect.test.ts @@ -1,4 +1,5 @@ import { + Status, StatusEffect, getStatusEffectActivationText, getStatusEffectDescriptor, @@ -6,14 +7,19 @@ import { getStatusEffectObtainText, getStatusEffectOverlapText, } from "#app/data/status-effect"; +import { MoveResult } from "#app/field/pokemon"; +import GameManager from "#app/test/utils/gameManager"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; import { mockI18next } from "#test/utils/testUtils"; import i18next from "i18next"; -import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const pokemonName = "PKM"; const sourceText = "SOURCE"; -describe("status-effect", () => { +describe("Status Effect Messages", () => { beforeAll(() => { i18next.init(); }); @@ -299,3 +305,59 @@ describe("status-effect", () => { vi.resetAllMocks(); }); }); + +describe("Status Effects - Sleep", () => { + 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.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should last the appropriate number of turns", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const player = game.scene.getPlayerPokemon()!; + player.status = new Status(StatusEffect.SLEEP, 0, 4); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status.effect).toBe(StatusEffect.SLEEP); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status.effect).toBe(StatusEffect.SLEEP); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status.effect).toBe(StatusEffect.SLEEP); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBeUndefined(); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); +}); 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 63c7b6245f5..583e302126c 100644 --- a/src/test/items/toxic_orb.test.ts +++ b/src/test/items/toxic_orb.test.ts @@ -7,8 +7,6 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; - describe("Items - Toxic orb", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -27,10 +25,10 @@ describe("Items - Toxic orb", () => { game = new GameManager(phaserGame); game.override .battleType("single") - .enemySpecies(Species.RATTATA) + .enemySpecies(Species.MAGIKARP) .ability(Abilities.BALL_FETCH) .enemyAbility(Abilities.BALL_FETCH) - .moveset([ Moves.SPLASH ]) + .moveset(Moves.SPLASH) .enemyMoveset(Moves.SPLASH) .startingHeldItems([{ name: "TOXIC_ORB", @@ -39,22 +37,19 @@ describe("Items - Toxic orb", () => { vi.spyOn(i18next, "t"); }); - it("badly poisons the holder", async () => { - await game.classicMode.startBattle([ Species.MIGHTYENA ]); + it("should badly poison the holder", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); - const player = game.scene.getPlayerField()[0]; + const player = game.scene.getPlayerPokemon()!; + expect(player.getHeldItems()[0].type.id).toBe("TOXIC_ORB"); game.move.select(Moves.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); - // Toxic orb should trigger here - await game.phaseInterceptor.run("MessagePhase"); + await game.phaseInterceptor.to("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); + expect(player.status?.toxicTurnCount).toBe(0); + }); }); diff --git a/src/test/moves/aurora_veil.test.ts b/src/test/moves/aurora_veil.test.ts index e71d4ab9d11..e68117a2f59 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, attacker, 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/light_screen.test.ts b/src/test/moves/light_screen.test.ts index 2308458003d..af14d9273e6 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, attacker, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/nightmare.test.ts b/src/test/moves/nightmare.test.ts new file mode 100644 index 00000000000..f4c485ff1b4 --- /dev/null +++ b/src/test/moves/nightmare.test.ts @@ -0,0 +1,52 @@ +import { StatusEffect } from "#app/data/status-effect"; +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("Moves - Nightmare", () => { + 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.RATTATA) + .enemyMoveset(Moves.SPLASH) + .enemyAbility(Abilities.BALL_FETCH) + .enemyStatusEffect(StatusEffect.SLEEP) + .startingLevel(5) + .moveset([ Moves.NIGHTMARE, Moves.SPLASH ]); + }); + + it("lowers enemy hp by 1/4 each turn while asleep", async () => { + await game.classicMode.startBattle([ Species.HYPNO ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const enemyMaxHP = enemyPokemon.hp; + + game.move.select(Moves.NIGHTMARE); + await game.toNextTurn(); + + expect(enemyPokemon.hp).toBe(enemyMaxHP - Math.floor(enemyMaxHP / 4)); + + // take a second turn to make sure damage occurs again + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(enemyPokemon.hp).toBe(enemyMaxHP - Math.floor(enemyMaxHP / 4) - Math.floor(enemyMaxHP / 4)); + }); +}); 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..3bf415ea75c 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, attacker, move.category, multiplierHolder); } return move.power * multiplierHolder.value; 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/toxic_spikes.test.ts b/src/test/moves/toxic_spikes.test.ts index bac1ccdccd8..a5c63a2652f 100644 --- a/src/test/moves/toxic_spikes.test.ts +++ b/src/test/moves/toxic_spikes.test.ts @@ -9,8 +9,6 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; - describe("Moves - Toxic Spikes", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -34,10 +32,10 @@ describe("Moves - Toxic Spikes", () => { .enemyAbility(Abilities.BALL_FETCH) .ability(Abilities.BALL_FETCH) .enemyMoveset(Moves.SPLASH) - .moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR ]); + .moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR, Moves.COURT_CHANGE ]); }); - it("should not affect the opponent if they do not switch", async() => { + 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]; @@ -51,9 +49,9 @@ describe("Moves - Toxic Spikes", () => { expect(enemy.hp).toBe(enemy.getMaxHp()); expect(enemy.status?.effect).toBeUndefined(); - }, TIMEOUT); + }); - it("should poison the opponent if they switch into 1 layer", async() => { + it("should poison the opponent if they switch into 1 layer", async () => { await game.classicMode.runToSummon([ Species.MIGHTYENA ]); game.move.select(Moves.TOXIC_SPIKES); @@ -65,9 +63,9 @@ describe("Moves - Toxic Spikes", () => { expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); expect(enemy.status?.effect).toBe(StatusEffect.POISON); - }, TIMEOUT); + }); - it("should badly poison the opponent if they switch into 2 layers", async() => { + 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); @@ -80,27 +78,32 @@ describe("Moves - Toxic Spikes", () => { const enemy = game.scene.getEnemyField()[0]; expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); expect(enemy.status?.effect).toBe(StatusEffect.TOXIC); - }, TIMEOUT); + }); - it("should be removed if a grounded poison pokemon switches in", async() => { - game.override.enemySpecies(Species.GRIMER); - await game.classicMode.runToSummon([ Species.MIGHTYENA ]); + 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.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).toBe(enemy.getMaxHp()); - expect(enemy.status?.effect).toBeUndefined(); + 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); - }, TIMEOUT); + }); - it("shouldn't create multiple layers per use in doubles", async() => { + 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); @@ -109,9 +112,9 @@ describe("Moves - Toxic Spikes", () => { 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); - }, TIMEOUT); + }); - it("should persist through reload", async() => { + it("should persist through reload", async () => { game.override.startingWave(1); const scene = game.scene; const gameData = new GameData(scene); @@ -132,5 +135,5 @@ describe("Moves - Toxic Spikes", () => { expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags); localStorage.removeItem("sessionTestData"); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/will_o_wisp.test.ts b/src/test/moves/will_o_wisp.test.ts new file mode 100644 index 00000000000..39729d331ad --- /dev/null +++ b/src/test/moves/will_o_wisp.test.ts @@ -0,0 +1,53 @@ +import { BattlerIndex } from "#app/battle"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Will-O-Wisp", () => { + 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.WILL_O_WISP, Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should burn the opponent", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const enemy = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.WILL_O_WISP); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.move.forceHit(); + await game.toNextTurn(); + + expect(enemy.status?.effect).toBe(StatusEffect.BURN); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(enemy.status?.effect).toBe(StatusEffect.BURN); + }); +}); 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/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/the-expert-breeder-encounter.test.ts b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index bbb4f249feb..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); @@ -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); @@ -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); 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/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/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/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 4e0e2feea81..a1ced6145eb 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -245,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; } } @@ -316,22 +316,22 @@ export default class AchvsUiHandler extends MessageUiHandler { if (update || pageChange) { switch (this.currentPage) { - 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; + 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; @@ -358,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; } 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/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 2f65fa0b457..0f5edc28675 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -89,54 +89,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 3aa009b1b31..8f977ba2ac0 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -127,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"); @@ -470,12 +470,12 @@ export default class EggGachaUiHandler extends MessageUiHandler { getGuaranteedEggTierFromPullCount(pullCount: number): EggTier { switch (pullCount) { - case 10: - return EggTier.RARE; - case 25: - return EggTier.EPIC; - default: - return EggTier.COMMON; + case 10: + return EggTier.RARE; + case 25: + return EggTier.EPIC; + default: + return EggTier.COMMON; } } @@ -567,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; } } @@ -638,106 +638,106 @@ export default class EggGachaUiHandler extends MessageUiHandler { } } 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 || 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) + 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 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { - 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 || 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")); - } - 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 adebf9fb912..0f4c2d2f53e 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -468,148 +468,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, -98); + }); + } 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, -98); }); } 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, -98); - }); - } else { - doLogout(); - } - break; + break; } } else if (button === Button.CANCEL) { success = true; @@ -620,20 +620,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 f1b8ed981ee..5ae4707e329 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -56,18 +56,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 20de7fd832c..061f15d0956 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -118,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; } } @@ -333,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 39927f8e071..0ca47241136 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -229,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); @@ -474,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. @@ -577,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; } } } @@ -911,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) { @@ -960,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 2e664db8d43..7bfecb1f99b 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -106,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; } @@ -151,26 +151,26 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } else { const cursorPosition = this.cursor + this.scrollCursor; switch (button) { - case Button.UP: - if (this.cursor) { + 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; - } + 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; + } } } 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 2be89052bc1..bb999dc736a 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1303,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 { @@ -1856,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; } } } @@ -2210,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); @@ -2323,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); @@ -2532,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; }); @@ -3362,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)); 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 22dd3f4cd6a..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.RARE: - return getModifierTierTextTint(ModifierTier.GREAT); - case EggTier.EPIC: - return getModifierTierTextTint(ModifierTier.ULTRA); - case EggTier.LEGENDARY: - 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 c2ee7100909..8a35a4b3f07 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -194,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); @@ -487,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; } }