diff --git a/eslint.config.js b/eslint.config.js index 5bd5d605d38..2f2b466c66f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -16,15 +16,15 @@ export default [ '@typescript-eslint': tseslint }, rules: { - "eqeqeq": ["error", "always"], // Enforces the use of === and !== instead of == and != - "indent": ["error", 2, { "SwitchCase": 1 }], // Enforces a 2-space indentation + "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 96d31210455..9723750a659 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"; @@ -1214,12 +1215,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; } } @@ -1397,7 +1396,7 @@ export default class BattleScene extends SceneBase { case Species.GRENINJA: return Utils.randSeedInt(2); case Species.ZYGARDE: - return Utils.randSeedInt(3); + return Utils.randSeedInt(4); case Species.MINIOR: return Utils.randSeedInt(6); case Species.ALCREMIE: @@ -2371,6 +2370,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 @@ -2433,7 +2445,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); } @@ -2490,6 +2502,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)) { @@ -3060,7 +3074,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++; @@ -3130,18 +3144,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; @@ -3182,6 +3204,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 8b8df3ddf61..33f6e0522f7 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -4913,8 +4913,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 +4957,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)), @@ -5535,16 +5533,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 c1c0a4d2d47..6507d34dcfd 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1,7 +1,7 @@ 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"; @@ -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; } @@ -122,10 +122,20 @@ export class MistTag extends ArenaTag { } } - apply(arena: Arena, args: any[]): boolean { - (args[0] as Utils.BooleanHolder).value = true; + /** + * Cancels the lowering of stats + * @param arena the {@linkcode Arena} containing this effect + * @param simulated `true` if the effect should be applied quietly + * @param cancelled a {@linkcode BooleanHolder} whose value is set to `true` + * to flag the stat reduction as cancelled + * @returns `true` if a stat reduction was cancelled; `false` otherwise + */ + override apply(arena: Arena, simulated: boolean, cancelled: BooleanHolder): boolean { + cancelled.value = true; - arena.scene.queueMessage(i18next.t("arenaTag:mistApply")); + if (!simulated) { + arena.scene.queueMessage(i18next.t("arenaTag:mistApply")); + } return true; } @@ -157,17 +167,15 @@ export class WeakenMoveScreenTag extends ArenaTag { /** * Applies the weakening effect to the move. * - * @param arena - The arena where the move is applied. - * @param args - The arguments for the move application. - * @param args[0] - The category of the move. - * @param args[1] - A boolean indicating whether it is a double battle. - * @param args[2] - An object of type `Utils.NumberHolder` that holds the damage multiplier - * - * @returns True if the move was weakened, otherwise false. + * @param arena the {@linkcode Arena} where the move is applied. + * @param simulated n/a + * @param moveCategory the attacking move's {@linkcode MoveCategory}. + * @param damageMultiplier A {@linkcode NumberHolder} containing the damage multiplier + * @returns `true` if the attacking move was weakened; `false` otherwise. */ - apply(arena: Arena, args: any[]): boolean { - if (this.weakenedCategories.includes((args[0] as MoveCategory))) { - (args[2] as Utils.NumberHolder).value = (args[1] as boolean) ? 2732 / 4096 : 0.5; + override apply(arena: Arena, simulated: boolean, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean { + if (this.weakenedCategories.includes(moveCategory)) { + damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5; return true; } return false; @@ -249,38 +257,34 @@ export class ConditionalProtectTag extends ArenaTag { onRemove(arena: Arena): void { } /** - * apply(): Checks incoming moves against the condition function + * Checks incoming moves against the condition function * and protects the target if conditions are met - * @param arena The arena containing this tag - * @param args\[0\] (Utils.BooleanHolder) Signals if the move is cancelled - * @param args\[1\] (Pokemon) The Pokemon using the move - * @param args\[2\] (Pokemon) The intended target of the move - * @param args\[3\] (Moves) The parameters to the condition function - * @param args\[4\] (Utils.BooleanHolder) Signals if the applied protection supercedes protection-ignoring effects - * @returns + * @param arena the {@linkcode Arena} containing this tag + * @param simulated `true` if the tag is applied quietly; `false` otherwise. + * @param isProtected a {@linkcode BooleanHolder} used to flag if the move is protected against + * @param attacker the attacking {@linkcode Pokemon} + * @param defender the defending {@linkcode Pokemon} + * @param moveId the {@linkcode Moves | identifier} for the move being used + * @param ignoresProtectBypass a {@linkcode BooleanHolder} used to flag if a protection effect supercedes effects that ignore protection + * @returns `true` if this tag protected against the attack; `false` otherwise */ - apply(arena: Arena, args: any[]): boolean { - const [ cancelled, user, target, moveId, ignoresBypass ] = args; + override apply(arena: Arena, simulated: boolean, isProtected: BooleanHolder, attacker: Pokemon, defender: Pokemon, + moveId: Moves, ignoresProtectBypass: BooleanHolder): boolean { - if (cancelled instanceof Utils.BooleanHolder - && user instanceof Pokemon - && target instanceof Pokemon - && typeof moveId === "number" - && ignoresBypass instanceof Utils.BooleanHolder) { + if ((this.side === ArenaTagSide.PLAYER) === defender.isPlayer() + && this.protectConditionFunc(arena, moveId)) { + if (!isProtected.value) { + isProtected.value = true; + if (!simulated) { + attacker.stopMultiHit(defender); - if ((this.side === ArenaTagSide.PLAYER) === target.isPlayer() - && this.protectConditionFunc(arena, moveId)) { - if (!cancelled.value) { - cancelled.value = true; - user.stopMultiHit(target); - - new CommonBattleAnim(CommonAnim.PROTECT, target).play(arena.scene); - arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + new CommonBattleAnim(CommonAnim.PROTECT, defender).play(arena.scene); + arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender) })); } - - ignoresBypass.value = ignoresBypass.value || this.ignoresBypass; - return true; } + + ignoresProtectBypass.value = ignoresProtectBypass.value || this.ignoresBypass; + return true; } return false; } @@ -296,7 +300,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) { @@ -460,7 +464,7 @@ class WishTag extends ArenaTag { if (user) { this.battlerIndex = user.getBattlerIndex(); this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) }); - this.healHp = Utils.toDmgValue(user.getMaxHp() / 2); + this.healHp = toDmgValue(user.getMaxHp() / 2); } else { console.warn("Failed to get source for WishTag onAdd"); } @@ -497,12 +501,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 +574,12 @@ export class IonDelugeTag extends ArenaTag { /** * Converts Normal-type moves to Electric type * @param arena n/a - * @param args - * - `[0]` {@linkcode Utils.NumberHolder} A container with a move's {@linkcode Type} + * @param simulated n/a + * @param moveType a {@linkcode NumberHolder} containing a move's {@linkcode Type} * @returns `true` if the given move type changed; `false` otherwise. */ - apply(arena: Arena, args: any[]): boolean { - const moveType = args[0]; - if (moveType instanceof Utils.NumberHolder && moveType.value === Type.NORMAL) { + override apply(arena: Arena, simulated: boolean, moveType: NumberHolder): boolean { + if (moveType.value === Type.NORMAL) { moveType.value = Type.ELECTRIC; return true; } @@ -608,16 +618,22 @@ export class ArenaTrapTag extends ArenaTag { } } - apply(arena: Arena, args: any[]): boolean { - const pokemon = args[0] as Pokemon; + /** + * 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.sourceId === pokemon.id || (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 +667,18 @@ class SpikesTag extends ArenaTrapTag { } } - activateTrap(pokemon: Pokemon): boolean { + override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { if (pokemon.isGrounded()) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + if (simulated) { + return !cancelled.value; + } + if (!cancelled.value) { const damageHpRatio = 1 / (10 - 2 * this.layers); - const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio); + const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); pokemon.scene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.damageAndUpdate(damage, HitResult.OTHER); @@ -702,8 +722,11 @@ class ToxicSpikesTag extends ArenaTrapTag { } } - activateTrap(pokemon: Pokemon): boolean { + override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { if (pokemon.isGrounded()) { + if (simulated) { + return true; + } if (pokemon.isOfType(Type.POISON)) { this.neutralized = true; if (pokemon.scene.arena.removeTag(this.tagType)) { @@ -807,8 +830,8 @@ class StealthRockTag extends ArenaTrapTag { 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 +841,16 @@ class StealthRockTag extends ArenaTrapTag { const damageHpRatio = this.getDamageHpRatio(pokemon); if (damageHpRatio) { - const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio); + if (simulated) { + return true; + } + const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); pokemon.scene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.damageAndUpdate(damage, HitResult.OTHER); if (pokemon.turnData) { pokemon.turnData.damageTaken += damage; } + return true; } return false; @@ -853,14 +880,20 @@ class StickyWebTag extends ArenaTrapTag { } } - activateTrap(pokemon: Pokemon): boolean { + override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { if (pokemon.isGrounded()) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); + + if (simulated) { + return !cancelled.value; + } + if (!cancelled.value) { pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() })); - const stages = new Utils.NumberHolder(-1); + const stages = new NumberHolder(-1); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value)); + return true; } } @@ -879,8 +912,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 +1127,7 @@ class FireGrassPledgeTag extends ArenaTag { pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // TODO: Replace this with a proper animation pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM)); - pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8)); + pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8)); }); return super.lapse(arena); @@ -1111,8 +1151,15 @@ class WaterFirePledgeTag extends ArenaTag { arena.scene.queueMessage(i18next.t(`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } - override apply(arena: Arena, args: any[]): boolean { - const moveChance = args[0] as Utils.NumberHolder; + /** + * Doubles the chance for the given move's secondary effect(s) to trigger + * @param arena the {@linkcode Arena} containing this tag + * @param simulated n/a + * @param moveChance a {@linkcode NumberHolder} containing + * the move's current effect chance + * @returns `true` if the move's effect chance was doubled (currently always `true`) + */ + override apply(arena: Arena, simulated: boolean, moveChance: NumberHolder): boolean { moveChance.value *= 2; return true; } diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index 2014bd3777b..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} diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 219f32129bd..4977a8da5a9 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2724,6 +2724,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 @@ -2899,6 +2937,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new SyrupBombTag(sourceId); case BattlerTagType.TELEKINESIS: return new TelekinesisTag(sourceMove); + case BattlerTagType.POWER_TRICK: + return new PowerTrickTag(sourceMove, sourceId); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/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/move.ts b/src/data/move.ts index 97e2927e692..b277462211a 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -808,7 +808,7 @@ export default class Move implements Localizable { source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power); if (!this.hasAttr(TypelessAttr)) { - source.scene.arena.applyTags(WeakenMoveTypeTag, this.type, power); + source.scene.arena.applyTags(WeakenMoveTypeTag, simulated, this.type, power); source.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power); } @@ -1024,9 +1024,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) { @@ -2875,6 +2875,162 @@ export class StatStageChangeAttr extends MoveEffectAttr { } } +/** + * Attribute used to determine the Biome/Terrain-based secondary effect of Secret Power + */ +export class SecretPowerAttr extends MoveEffectAttr { + constructor() { + super(false); + } + + /** + * Used to determine if the move should apply a secondary effect based on Secret Power's 30% chance + * @returns `true` if the move's secondary effect should apply + */ + override canApply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { + this.effectChanceOverride = move.chance; + const moveChance = this.getMoveChance(user, target, move, this.selfTarget); + if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { + return true; + } else { + return false; + } + } + + /** + * Used to apply the secondary effect to the target Pokemon + * @returns `true` if a secondary effect is successfully applied + */ + override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise { + if (!super.apply(user, target, move, args)) { + return false; + } + let secondaryEffect: MoveEffectAttr; + const terrain = user.scene.arena.getTerrainType(); + if (terrain !== TerrainType.NONE) { + secondaryEffect = this.determineTerrainEffect(terrain); + } else { + const biome = user.scene.arena.biomeType; + secondaryEffect = this.determineBiomeEffect(biome); + } + // effectChanceOverride used in the application of the actual secondary effect + secondaryEffect.effectChanceOverride = 100; + return secondaryEffect.apply(user, target, move, []); + } + + /** + * Determines the secondary effect based on terrain. + * Takes precedence over biome-based effects. + * ``` + * Electric Terrain | Paralysis + * Misty Terrain | SpAtk -1 + * Grassy Terrain | Sleep + * Psychic Terrain | Speed -1 + * ``` + * @param terrain - {@linkcode TerrainType} The current terrain + * @returns the chosen secondary effect {@linkcode MoveEffectAttr} + */ + private determineTerrainEffect(terrain: TerrainType): MoveEffectAttr { + let secondaryEffect: MoveEffectAttr; + switch (terrain) { + case TerrainType.ELECTRIC: + default: + secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); + break; + case TerrainType.MISTY: + secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); + break; + case TerrainType.GRASSY: + secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false); + break; + case TerrainType.PSYCHIC: + secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); + break; + } + return secondaryEffect; + } + + /** + * Determines the secondary effect based on biome + * ``` + * Town, Metropolis, Slum, Dojo, Laboratory, Power Plant + Default | Paralysis + * Plains, Grass, Tall Grass, Forest, Jungle, Meadow | Sleep + * Swamp, Mountain, Temple, Ruins | Speed -1 + * Ice Cave, Snowy Forest | Freeze + * Volcano | Burn + * Fairy Cave | SpAtk -1 + * Desert, Construction Site, Beach, Island, Badlands | Accuracy -1 + * Sea, Lake, Seabed | Atk -1 + * Cave, Wasteland, Graveyard, Abyss, Space | Flinch + * End | Def -1 + * ``` + * @param biome - The current {@linkcode Biome} the battle is set in + * @returns the chosen secondary effect {@linkcode MoveEffectAttr} + */ + private determineBiomeEffect(biome: Biome): MoveEffectAttr { + let secondaryEffect: MoveEffectAttr; + switch (biome) { + case Biome.PLAINS: + case Biome.GRASS: + case Biome.TALL_GRASS: + case Biome.FOREST: + case Biome.JUNGLE: + case Biome.MEADOW: + secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false); + break; + case Biome.SWAMP: + case Biome.MOUNTAIN: + case Biome.TEMPLE: + case Biome.RUINS: + secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); + break; + case Biome.ICE_CAVE: + case Biome.SNOWY_FOREST: + secondaryEffect = new StatusEffectAttr(StatusEffect.FREEZE, false); + break; + case Biome.VOLCANO: + secondaryEffect = new StatusEffectAttr(StatusEffect.BURN, false); + break; + case Biome.FAIRY_CAVE: + secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); + break; + case Biome.DESERT: + case Biome.CONSTRUCTION_SITE: + case Biome.BEACH: + case Biome.ISLAND: + case Biome.BADLANDS: + secondaryEffect = new StatStageChangeAttr([ Stat.ACC ], -1, false); + break; + case Biome.SEA: + case Biome.LAKE: + case Biome.SEABED: + secondaryEffect = new StatStageChangeAttr([ Stat.ATK ], -1, false); + break; + case Biome.CAVE: + case Biome.WASTELAND: + case Biome.GRAVEYARD: + case Biome.ABYSS: + case Biome.SPACE: + secondaryEffect = new AddBattlerTagAttr(BattlerTagType.FLINCHED, false, true); + break; + case Biome.END: + secondaryEffect = new StatStageChangeAttr([ Stat.DEF ], -1, false); + break; + case Biome.TOWN: + case Biome.METROPOLIS: + case Biome.SLUM: + case Biome.DOJO: + case Biome.FACTORY: + case Biome.LABORATORY: + case Biome.POWER_PLANT: + default: + secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); + break; + } + return secondaryEffect; + } +} + export class PostVictoryStatStageChangeAttr extends MoveAttr { private stats: BattleStat[]; private stages: number; @@ -3834,8 +3990,8 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr { for (const p of pokemonActed) { const [ lastMove ] = p.getLastXMoves(1); - if (lastMove.result !== MoveResult.FAIL) { - if ((lastMove.result === MoveResult.SUCCESS) && (lastMove.move === this.move)) { + if (lastMove?.result !== MoveResult.FAIL) { + if ((lastMove?.result === MoveResult.SUCCESS) && (lastMove?.move === this.move)) { power.value *= 2; return true; } else { @@ -4736,7 +4892,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr { } canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0].result === MoveResult.FAIL)) { + if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0]?.result === MoveResult.FAIL)) { return false; } else { return true; @@ -5174,7 +5330,7 @@ export class AddArenaTagAttr extends MoveEffectAttr { return false; } - if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0].result === MoveResult.SUCCESS) { + if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); return true; } @@ -5249,7 +5405,7 @@ export class AddArenaTrapTagHitAttr extends AddArenaTagAttr { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag; - if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) && user.getLastXMoves(1)[0].result === MoveResult.SUCCESS) { + if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { user.scene.arena.addTag(this.tagType, 0, move.id, user.id, side); if (!tag) { return true; @@ -5386,7 +5542,7 @@ export class AddPledgeEffectAttr extends AddArenaTagAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { // TODO: add support for `HIT` effect triggering in AddArenaTagAttr to remove the need for this check - if (user.getLastXMoves(1)[0].result !== MoveResult.SUCCESS) { + if (user.getLastXMoves(1)[0]?.result !== MoveResult.SUCCESS) { return false; } @@ -6430,6 +6586,9 @@ export class TransformAttr extends MoveEffectAttr { user.summonData.gender = target.getGender(); user.summonData.fusionGender = target.getFusionGender(); + // Power Trick's effect will not preserved after using Transform + user.removeTag(BattlerTagType.POWER_TRICK); + // Copy all stats (except HP) for (const s of EFFECTIVE_STATS) { user.setStat(s, target.getStat(s, false), false); @@ -7895,7 +8054,7 @@ export function initMoves() { .unimplemented(), new AttackMove(Moves.SECRET_POWER, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, 30, 0, 3) .makesContact(false) - .partial(), // 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 +8312,7 @@ export function initMoves() { .attr(OpponentHighHpPowerAttr, 120) .makesContact(), new SelfStatusMove(Moves.POWER_TRICK, Type.PSYCHIC, -1, 10, -1, 0, 4) - .unimplemented(), + .attr(AddBattlerTagAttr, BattlerTagType.POWER_TRICK, true), new StatusMove(Moves.GASTRO_ACID, Type.POISON, 100, 10, -1, 0, 4) .attr(SuppressAbilitiesAttr), new StatusMove(Moves.LUCKY_CHANT, Type.NORMAL, -1, 30, -1, 0, 4) 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 1bbe32c084a..f0155b4f2a4 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -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 d95b241a482..fa8e1aed1c7 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 7da39e56a62..3c5948af500 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 c3dfefc3512..e3189e106ed 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 ac81b7632f7..bae5a8790e9 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 2556b1e9cbe..139e9ac7f13 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 8fe57b4ed07..3f9030dc3b2 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 57d0ec2bf75..7bf48aa5926 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 8627e497872..1d65fd6c9a6 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 ea9e1684cdc..a8cb076bbe9 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 c3a66caf864..ab6517e97af 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 e1857ff206b..ec3e1bdaa35 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 4e0000069be..faeba95f358 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 c47e2991bd3..94abef70b37 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.getPlayerParty().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 31ceb933bab..b1ef6cedb21 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 67fba86eb9d..7d9b531c9ab 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 dac27cd5c1d..b02aa3269ac 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 3e7972bac41..8980327ee58 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 12b8a8547d5..a2c32c6af40 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 a249f2d5f5e..162fb734e1e 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 a9da1932af4..6d220a4ee8a 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -1,4 +1,5 @@ import BattleScene from "#app/battle-scene"; +import { allAbilities } from "#app/data/ability"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { Nature } from "#app/data/nature"; import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; @@ -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 ecaed5f8d63..a4b5853dfa4 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.getPlayerParty().filter(p => !p.isFainted())[0]; + this.primaryPokemon = scene.getPlayerParty().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 fdfa5feacf9..75df92b6a07 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 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 b4d7c2f7361..947ac939989 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -425,6 +425,7 @@ export abstract class PokemonSpeciesForm { case "hero": case "roaming": case "complete": + case "10-complete": case "10": case "10-pc": case "super": @@ -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/trainer-config.ts b/src/data/trainer-config.ts index f8f1e5ffb50..fcc13975270 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -574,13 +574,13 @@ export class TrainerConfig { 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.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 ], + [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 ] }; @@ -601,9 +601,9 @@ export class TrainerConfig { } 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.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.NOIVERN, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ] + [TrainerPoolTier.RARE]: [ Species.NOIBAT, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ] }; } case "aether": { @@ -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/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/arena.ts b/src/field/arena.ts index 2ddd4b98d14..7bfdf9a0000 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -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); } /** diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index b2cc88ea528..da473c7a327 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5,7 +5,7 @@ import { variantData } from "#app/data/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info"; import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; -import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; +import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "#app/utils"; @@ -19,7 +19,7 @@ import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { Status, StatusEffect, getRandomStatus } from "#app/data/status-effect"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; -import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } from "../data/battler-tags"; +import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags"; import { WeatherType } from "#app/data/weather"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "#app/data/ability"; @@ -62,7 +62,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph import { Challenges } from "#enums/challenges"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { SwitchType } from "#enums/switch-type"; import { SpeciesFormKey } from "#enums/species-form-key"; import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates"; @@ -114,7 +114,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public fusionVariant: Variant; public fusionGender: Gender; public fusionLuck: integer; - public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData | null; + public fusionCustomPokemonData: CustomPokemonData | null; private summonDataPrimer: PokemonSummonData | null; @@ -122,7 +122,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public battleData: PokemonBattleData; public battleSummonData: PokemonBattleSummonData; public turnData: PokemonTurnData; - public mysteryEncounterPokemonData: MysteryEncounterPokemonData; + public customPokemonData: CustomPokemonData; /** Used by Mystery Encounters to execute pokemon-specific logic (such as stat boosts) at start of battle */ public mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; @@ -193,7 +193,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.nature = dataSource.nature || 0 as Nature; this.nickname = dataSource.nickname; - this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1; this.moveset = dataSource.moveset; this.status = dataSource.status!; // TODO: is this bang correct? this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship; @@ -212,9 +211,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionVariant = dataSource.fusionVariant || 0; this.fusionGender = dataSource.fusionGender; this.fusionLuck = dataSource.fusionLuck; - this.fusionMysteryEncounterPokemonData = dataSource.fusionMysteryEncounterPokemonData; + this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData; this.usedTMs = dataSource.usedTMs ?? []; - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(dataSource.mysteryEncounterPokemonData); + this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData); } else { this.id = Utils.randSeedInt(4294967296); this.ivs = ivs || Utils.getIvsFromId(this.id); @@ -235,7 +234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.variant = this.shiny ? this.generateVariant() : 0; } - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + this.customPokemonData = new CustomPokemonData(); if (nature !== undefined) { this.setNature(nature); @@ -243,8 +242,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.generateNature(); } - this.natureOverride = -1; - this.friendship = species.baseFriendship; this.metLevel = level; this.metBiome = scene.currentBattle ? scene.arena.biomeType : -1; @@ -603,8 +600,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; } @@ -1033,7 +1030,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 { @@ -1208,15 +1205,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]) { @@ -1228,8 +1225,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); @@ -1240,7 +1237,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]) { @@ -1272,6 +1269,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; } @@ -1298,14 +1300,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) { @@ -1328,8 +1330,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; @@ -1548,7 +1550,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; } @@ -2028,7 +2030,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(); @@ -2197,7 +2199,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 { @@ -2615,7 +2620,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */ const screenMultiplier = new Utils.NumberHolder(1); - this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier); + this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, moveCategory, screenMultiplier); /** * For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if: @@ -2820,15 +2825,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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; } } @@ -3058,6 +3064,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { continue; } + if (tag instanceof PowerTrickTag) { + tag.swapStat(this); + } + this.summonData.tags.push(tag); } @@ -4087,7 +4097,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); @@ -4297,12 +4307,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 = () => { @@ -4339,7 +4370,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; } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 27f0663347f..8e7853a41bb 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -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); @@ -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) { @@ -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), diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 755633f4d32..4e558a030ef 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; @@ -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.getPlayerParty().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.getPlayerParty().indexOf(playerPokemon), this.type.moveId, true)); + playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getPlayerParty().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.getPlayerParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex])); + override apply(playerPokemon: PlayerPokemon, cost?: number): boolean { + + playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getPlayerParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], LearnMoveType.MEMORY, cost)); return true; } 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 1261184c4ae..c4d919c0325 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -35,6 +35,7 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import i18next from "i18next"; +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.getPlayerParty()) { if (pokemon) { pokemon.resetBattleData(); diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 623e4f85896..b08ae3f217a 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -1,13 +1,13 @@ import { BattlerIndex, BattleType } from "#app/battle"; import BattleScene from "#app/battle-scene"; import { applyPostFaintAbAttrs, applyPostKnockOutAbAttrs, applyPostVictoryAbAttrs, PostFaintAbAttr, PostKnockOutAbAttr, 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 { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { BattleSpec } from "#app/enums/battle-spec"; import { StatusEffect } from "#app/enums/status-effect"; -import { EnemyPokemon, HitResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import Pokemon, { EnemyPokemon, HitResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { SwitchType } from "#enums/switch-type"; @@ -19,19 +19,40 @@ import { SwitchPhase } from "./switch-phase"; import { SwitchSummonPhase } from "./switch-summon-phase"; import { ToggleDoublePositionPhase } from "./toggle-double-position-phase"; import { VictoryPhase } from "./victory-phase"; +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"); diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index 6480577258a..eb7cfbb65ef 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,11 +149,23 @@ 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(() => { diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 581cd5ff017..dc880f85e23 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 diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 38fb5253062..0af61918636 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -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(); } } diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index e11c6cfe0bd..e7d1f7e9074 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.getPlayerParty().filter(p => !p.isFainted()); + const availablePartyMembers = scene.getPlayerParty().filter(p => p.isAllowedInBattle()); if (!availablePartyMembers[0].isOnField()) { scene.pushPhase(new SummonPhase(scene, 0)); 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/post-summon-phase.ts b/src/phases/post-summon-phase.ts index b99c0b90fd8..617bb8b1cfe 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -20,7 +20,7 @@ export class PostSummonPhase extends PokemonPhase { if (pokemon.status?.effect === StatusEffect.TOXIC) { pokemon.status.turnCount = 0; } - this.scene.arena.applyTags(ArenaTrapTag, pokemon); + this.scene.arena.applyTags(ArenaTrapTag, false, pokemon); // If this is mystery encounter and has post summon phase tag, apply post summon effects if (this.scene.currentBattle.isBattleMysteryEncounter() && pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag).length > 0) { diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 8028b6ff629..323294a8867 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.getPlayerParty(); - 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,13 +69,13 @@ 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) { @@ -79,7 +85,7 @@ export class SelectModifierPhase extends BattlePhase { 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.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) { @@ -98,13 +104,13 @@ export class SelectModifierPhase extends BattlePhase { 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)); + 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(), 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)); }); break; case 3: @@ -115,21 +121,21 @@ export class SelectModifierPhase extends BattlePhase { } this.scene.lockModifierTiers = !this.scene.lockModifierTiers; const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers)); uiHandler.updateLockRaritiesText(); uiHandler.updateRerollCostText(); return false; } return true; case 1: - if (typeOptions.length === 0) { + if (this.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; + if (this.typeOptions[cursor].type) { + modifierType = this.typeOptions[cursor].type; } break; default: @@ -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.getPlayerParty(), 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..4c13b883445 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -64,8 +64,7 @@ export class StatStageChangePhase extends PokemonPhase { const cancelled = new BooleanHolder(false); if (!this.selfTarget && stages.value < 0) { - // TODO: Include simulate boolean when tag applications can be simulated - this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled); + this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, cancelled); } if (!cancelled.value && !this.selfTarget && stages.value < 0) { diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index d40785e9aa4..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) => { diff --git a/src/system/game-data.ts b/src/system/game-data.ts index d400839a27a..ec7fac1e72e 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"; @@ -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)); } diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 8240b6bcf84..cddc5798872 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -12,7 +12,7 @@ import { loadBattlerTag } from "../data/battler-tags"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; export default class PokemonData { public id: integer; @@ -33,7 +33,6 @@ export default class PokemonData { public stats: integer[]; public ivs: integer[]; public nature: Nature; - public natureOverride: Nature | -1; public moveset: (PokemonMove | null)[]; public status: Status | null; public friendship: integer; @@ -54,14 +53,20 @@ export default class PokemonData { public fusionVariant: Variant; public fusionGender: Gender; public fusionLuck: integer; - public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData; public boss: boolean; public bossSegments?: integer; public summonData: PokemonSummonData; + /** Data that can customize a Pokemon in non-standard ways from its Species */ - public mysteryEncounterPokemonData: MysteryEncounterPokemonData; + public customPokemonData: CustomPokemonData; + public fusionCustomPokemonData: CustomPokemonData; + + // Deprecated attributes, needed for now to allow SessionData migration (see PR#4619 comments) + public natureOverride: Nature | -1; + public mysteryEncounterPokemonData: CustomPokemonData | null; + public fusionMysteryEncounterPokemonData: CustomPokemonData | null; constructor(source: Pokemon | any, forHistory: boolean = false) { const sourcePokemon = source instanceof Pokemon ? source : null; @@ -107,9 +112,13 @@ export default class PokemonData { this.fusionVariant = source.fusionVariant; this.fusionGender = source.fusionGender; this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0); + this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData); this.usedTMs = source.usedTMs ?? []; - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(source.mysteryEncounterPokemonData); + this.customPokemonData = new CustomPokemonData(source.customPokemonData); + + this.mysteryEncounterPokemonData = new CustomPokemonData(source.mysteryEncounterPokemonData); + this.fusionMysteryEncounterPokemonData = new CustomPokemonData(source.fusionMysteryEncounterPokemonData); if (!forHistory) { this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss); diff --git a/src/system/version-converter.ts b/src/system/version-converter.ts deleted file mode 100644 index ad62c5c8194..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/test/abilities/power_construct.test.ts b/src/test/abilities/power_construct.test.ts index f75cd1f6eee..bb80e9f3ac8 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.getPlayerParty().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.getPlayerParty().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/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/moves/aurora_veil.test.ts b/src/test/moves/aurora_veil.test.ts index e71d4ab9d11..243ba3a3269 100644 --- a/src/test/moves/aurora_veil.test.ts +++ b/src/test/moves/aurora_veil.test.ts @@ -111,7 +111,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (defender.scene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, move.category, defender.scene.currentBattle.double, multiplierHolder); + defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/destiny_bond.test.ts b/src/test/moves/destiny_bond.test.ts new file mode 100644 index 00000000000..4b4c8782862 --- /dev/null +++ b/src/test/moves/destiny_bond.test.ts @@ -0,0 +1,255 @@ +import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { allMoves } from "#app/data/move"; +import { Abilities } from "#enums/abilities"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { BattlerIndex } from "#app/battle"; +import { StatusEffect } from "#enums/status-effect"; +import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; + + +describe("Moves - Destiny Bond", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + const defaultParty = [ Species.BULBASAUR, Species.SQUIRTLE ]; + const enemyFirst = [ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]; + const playerFirst = [ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override.battleType("single") + .ability(Abilities.UNNERVE) // Pre-emptively prevent flakiness from opponent berries + .enemySpecies(Species.RATTATA) + .enemyAbility(Abilities.RUN_AWAY) + .startingLevel(100) // Make sure tested moves KO + .enemyLevel(5) + .enemyMoveset(Moves.DESTINY_BOND); + }); + + it("should KO the opponent on the same turn", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset(moveToUse); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + }); + + it("should KO the opponent on the next turn", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset([ Moves.SPLASH, moveToUse ]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + // Turn 1: Enemy uses Destiny Bond and doesn't faint + game.move.select(Moves.SPLASH); + await game.setTurnOrder(playerFirst); + await game.toNextTurn(); + + expect(enemyPokemon?.isFainted()).toBe(false); + expect(playerPokemon?.isFainted()).toBe(false); + + // Turn 2: Player KO's the enemy before the enemy's turn + game.move.select(moveToUse); + await game.setTurnOrder(playerFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + }); + + it("should fail if used twice in a row", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset([ Moves.SPLASH, moveToUse ]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + // Turn 1: Enemy uses Destiny Bond and doesn't faint + game.move.select(Moves.SPLASH); + await game.setTurnOrder(enemyFirst); + await game.toNextTurn(); + + expect(enemyPokemon?.isFainted()).toBe(false); + expect(playerPokemon?.isFainted()).toBe(false); + + // Turn 2: Enemy should fail Destiny Bond then get KO'd + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(false); + }); + + it("should not KO the opponent if the user dies to weather", async () => { + // Opponent will be reduced to 1 HP by False Swipe, then faint to Sandstorm + const moveToUse = Moves.FALSE_SWIPE; + + game.override.moveset(moveToUse) + .ability(Abilities.SAND_STREAM); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(false); + }); + + it("should not KO the opponent if the user had another turn", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset([ Moves.SPORE, moveToUse ]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + // Turn 1: Enemy uses Destiny Bond and doesn't faint + game.move.select(Moves.SPORE); + await game.setTurnOrder(enemyFirst); + await game.toNextTurn(); + + expect(enemyPokemon?.isFainted()).toBe(false); + expect(playerPokemon?.isFainted()).toBe(false); + expect(enemyPokemon?.status?.effect).toBe(StatusEffect.SLEEP); + + // Turn 2: Enemy should skip a turn due to sleep, then get KO'd + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(false); + }); + + it("should not KO an ally", async () => { + game.override.moveset([ Moves.DESTINY_BOND, Moves.CRUNCH ]) + .battleType("double"); + await game.classicMode.startBattle([ Species.SHEDINJA, Species.BULBASAUR, Species.SQUIRTLE ]); + + const enemyPokemon0 = game.scene.getEnemyField()[0]; + const enemyPokemon1 = game.scene.getEnemyField()[1]; + const playerPokemon0 = game.scene.getPlayerField()[0]; + const playerPokemon1 = game.scene.getPlayerField()[1]; + + // Shedinja uses Destiny Bond, then ally Bulbasaur KO's Shedinja with Crunch + game.move.select(Moves.DESTINY_BOND, 0); + game.move.select(Moves.CRUNCH, 1, BattlerIndex.PLAYER); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon0?.isFainted()).toBe(false); + expect(enemyPokemon1?.isFainted()).toBe(false); + expect(playerPokemon0?.isFainted()).toBe(true); + expect(playerPokemon1?.isFainted()).toBe(false); + }); + + it("should not cause a crash if the user is KO'd by Ceaseless Edge", async () => { + const moveToUse = Moves.CEASELESS_EDGE; + vi.spyOn(allMoves[moveToUse], "accuracy", "get").mockReturnValue(100); + + game.override.moveset(moveToUse); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + + // Ceaseless Edge spikes effect should still activate + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter.tagType).toBe(ArenaTagType.SPIKES); + expect(tagAfter.layers).toBe(1); + }); + + it("should not cause a crash if the user is KO'd by Pledge moves", async () => { + game.override.moveset([ Moves.GRASS_PLEDGE, Moves.WATER_PLEDGE ]) + .battleType("double"); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon0 = game.scene.getEnemyField()[0]; + const enemyPokemon1 = game.scene.getEnemyField()[1]; + const playerPokemon0 = game.scene.getPlayerField()[0]; + const playerPokemon1 = game.scene.getPlayerField()[1]; + + game.move.select(Moves.GRASS_PLEDGE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.WATER_PLEDGE, 1, BattlerIndex.ENEMY); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon0?.isFainted()).toBe(true); + expect(enemyPokemon1?.isFainted()).toBe(false); + expect(playerPokemon0?.isFainted()).toBe(false); + expect(playerPokemon1?.isFainted()).toBe(true); + + // Pledge secondary effect should still activate + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter.tagType).toBe(ArenaTagType.GRASS_WATER_PLEDGE); + }); + + /** + * In particular, this should prevent something like + * {@link https://github.com/pagefaultgames/pokerogue/issues/4219} + * from occurring with fainting by KO'ing a Destiny Bond user with U-Turn. + */ + it("should not allow the opponent to revive via Reviver Seed", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset(moveToUse) + .startingHeldItems([{ name: "REVIVER_SEED" }]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + + // Check that the Tackle user's Reviver Seed did not activate + const revSeeds = game.scene.getModifiers(PokemonInstantReviveModifier).filter(m => m.pokemonId === playerPokemon?.id); + expect(revSeeds.length).toBe(1); + }); +}); diff --git a/src/test/moves/light_screen.test.ts b/src/test/moves/light_screen.test.ts index 2308458003d..11b8144bb4e 100644 --- a/src/test/moves/light_screen.test.ts +++ b/src/test/moves/light_screen.test.ts @@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (defender.scene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, move.category, defender.scene.currentBattle.double, multiplierHolder); + defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/power_trick.test.ts b/src/test/moves/power_trick.test.ts new file mode 100644 index 00000000000..a064a43dec4 --- /dev/null +++ b/src/test/moves/power_trick.test.ts @@ -0,0 +1,113 @@ +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import { Moves } from "#enums/moves"; +import { Stat } from "#enums/stat"; +import { Species } from "#enums/species"; +import { TurnEndPhase } from "#app/phases/turn-end-phase"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; + +describe("Moves - Power Trick", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .enemySpecies(Species.MEW) + .enemyLevel(200) + .moveset([ Moves.POWER_TRICK ]) + .ability(Abilities.BALL_FETCH); + }); + + it("swaps the user's ATK and DEF stats", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE ]); + + const player = game.scene.getPlayerPokemon()!; + const baseATK = player.getStat(Stat.ATK, false); + const baseDEF = player.getStat(Stat.DEF, false); + + game.move.select(Moves.POWER_TRICK); + + await game.phaseInterceptor.to(TurnEndPhase); + + expect(player.getStat(Stat.ATK, false)).toBe(baseDEF); + expect(player.getStat(Stat.DEF, false)).toBe(baseATK); + expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeDefined(); + }); + + it("resets initial ATK and DEF stat swap when used consecutively", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE ]); + + const player = game.scene.getPlayerPokemon()!; + const baseATK = player.getStat(Stat.ATK, false); + const baseDEF = player.getStat(Stat.DEF, false); + + game.move.select(Moves.POWER_TRICK); + + await game.phaseInterceptor.to(TurnEndPhase); + + game.move.select(Moves.POWER_TRICK); + + await game.phaseInterceptor.to(TurnEndPhase); + + expect(player.getStat(Stat.ATK, false)).toBe(baseATK); + expect(player.getStat(Stat.DEF, false)).toBe(baseDEF); + expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined(); + }); + + it("should pass effect when using BATON_PASS", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]); + await game.override.moveset([ Moves.POWER_TRICK, Moves.BATON_PASS ]); + + const player = game.scene.getPlayerPokemon()!; + player.addTag(BattlerTagType.POWER_TRICK); + + game.move.select(Moves.BATON_PASS); + game.doSelectPartyPokemon(1); + + await game.phaseInterceptor.to(TurnEndPhase); + + const switchedPlayer = game.scene.getPlayerPokemon()!; + const baseATK = switchedPlayer.getStat(Stat.ATK); + const baseDEF = switchedPlayer.getStat(Stat.DEF); + + expect(switchedPlayer.getStat(Stat.ATK, false)).toBe(baseDEF); + expect(switchedPlayer.getStat(Stat.DEF, false)).toBe(baseATK); + expect(switchedPlayer.getTag(BattlerTagType.POWER_TRICK)).toBeDefined(); + }); + + it("should remove effect after using Transform", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]); + await game.override.moveset([ Moves.POWER_TRICK, Moves.TRANSFORM ]); + + const player = game.scene.getPlayerPokemon()!; + player.addTag(BattlerTagType.POWER_TRICK); + + game.move.select(Moves.TRANSFORM); + + await game.phaseInterceptor.to(TurnEndPhase); + + const enemy = game.scene.getEnemyPokemon()!; + const baseATK = enemy.getStat(Stat.ATK); + const baseDEF = enemy.getStat(Stat.DEF); + + expect(player.getStat(Stat.ATK, false)).toBe(baseATK); + expect(player.getStat(Stat.DEF, false)).toBe(baseDEF); + expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined(); + }); +}); diff --git a/src/test/moves/reflect.test.ts b/src/test/moves/reflect.test.ts index 41a10988552..b18b2423895 100644 --- a/src/test/moves/reflect.test.ts +++ b/src/test/moves/reflect.test.ts @@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (defender.scene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, move.category, defender.scene.currentBattle.double, multiplierHolder); + defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/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/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 9f281677065..7ea1f883bd1 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.getPlayerParty()[0]; - expect(leadPokemon.mysteryEncounterPokemonData?.ability).toBe(abilityToTrain); + expect(leadPokemon.customPokemonData?.ability).toBe(abilityToTrain); }); }); @@ -340,9 +340,9 @@ describe("Clowning Around - Mystery Encounter", () => { scene.getPlayerParty()[2].moveset = []; await runMysteryEncounterToEnd(game, 3); - const leadTypesAfter = scene.getPlayerParty()[0].mysteryEncounterPokemonData?.types; - const secondaryTypesAfter = scene.getPlayerParty()[1].mysteryEncounterPokemonData?.types; - const thirdTypesAfter = scene.getPlayerParty()[2].mysteryEncounterPokemonData?.types; + const leadTypesAfter = scene.getPlayerParty()[0].customPokemonData?.types; + const secondaryTypesAfter = scene.getPlayerParty()[1].customPokemonData?.types; + const thirdTypesAfter = scene.getPlayerParty()[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 3f29a978d25..7fc2490fcc9 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 0eb1e68cc14..be0e6e68b5e 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 c25a6c8d153..46546ec82ce 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 9022a45689d..60f81f3ad54 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/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index 1253ea8e6ae..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()); } @@ -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/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/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/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index d57f0227c0c..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); @@ -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")); } 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 12de9258661..a26aa572ef3 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -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); diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 6dc5ff8321e..0ca47241136 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -587,13 +587,13 @@ export default class RunInfoUiHandler extends UiHandler { 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; + 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; } } } 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),