diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000000..9bcccb9439d --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.13.1 diff --git a/package-lock.json b/package-lock.json index 676539af79e..4398037822a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pokemon-rogue-battle", - "version": "1.5.2", + "version": "1.5.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pokemon-rogue-battle", - "version": "1.5.2", + "version": "1.5.4", "hasInstallScript": true, "dependencies": { "@material/material-color-utilities": "^0.2.7", @@ -28,7 +28,7 @@ "@types/node": "^20.12.13", "@typescript-eslint/eslint-plugin": "^8.0.0-alpha.54", "@typescript-eslint/parser": "^8.0.0-alpha.54", - "@vitest/coverage-istanbul": "^2.0.4", + "@vitest/coverage-istanbul": "^2.1.9", "dependency-cruiser": "^16.3.10", "eslint": "^9.7.0", "eslint-plugin-import-x": "^4.2.1", @@ -40,9 +40,9 @@ "typedoc": "^0.26.4", "typescript": "^5.5.3", "typescript-eslint": "^8.0.0-alpha.54", - "vite": "^5.4.8", + "vite": "^5.4.14", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^2.0.4", + "vitest": "^2.1.9", "vitest-canvas-mock": "^0.3.3" }, "engines": { @@ -269,9 +269,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, "license": "MIT", "engines": { @@ -279,9 +279,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, "license": "MIT", "engines": { @@ -406,11 +406,14 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.0.tgz", - "integrity": "sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.8.tgz", + "integrity": "sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.8" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -476,15 +479,14 @@ } }, "node_modules/@babel/types": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.0.tgz", - "integrity": "sha512-LcnxQSsd9aXOIgmmSpvZ/1yo46ra2ESYyqLcryaBZOghxy5qqOBjvCWP5JfkI8yl9rlxRgdLTTMCQQRcN2hdCg==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.8.tgz", + "integrity": "sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2145,20 +2147,20 @@ } }, "node_modules/@vitest/coverage-istanbul": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-2.0.4.tgz", - "integrity": "sha512-6VibYMkXh8cJm5Bg8JYeOoR4oURlPf4YKP9kuVRE/NKasfYrXPnzSwuxrpgMbgOfPj13KUJXgMB3VAGukECtlQ==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-2.1.9.tgz", + "integrity": "sha512-vdYE4FkC/y2lxcN3Dcj54Bw+ericmDwiex0B8LV5F/YNYEYP1mgVwhPnHwWGAXu38qizkjOuyczKbFTALfzFKw==", "dev": true, "license": "MIT", "dependencies": { "@istanbuljs/schema": "^0.1.3", - "debug": "^4.3.5", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-instrument": "^6.0.3", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magicast": "^0.3.4", + "magicast": "^0.3.5", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, @@ -2166,29 +2168,56 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "2.0.4" + "vitest": "2.1.9" } }, "node_modules/@vitest/expect": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.4.tgz", - "integrity": "sha512-39jr5EguIoanChvBqe34I8m1hJFI4+jxvdOpD7gslZrVQBKhh8H9eD7J/LJX4zakrw23W+dITQTDqdt43xVcJw==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.0.4", - "@vitest/utils": "2.0.4", - "chai": "^5.1.1", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, "node_modules/@vitest/pretty-format": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.4.tgz", - "integrity": "sha512-RYZl31STbNGqf4l2eQM1nvKPXE0NhC6Eq0suTTePc4mtMQ1Fn8qZmjV4emZdEdG2NOWGKSCrHZjmTqDCDoeFBw==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2199,13 +2228,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.4.tgz", - "integrity": "sha512-Gk+9Su/2H2zNfNdeJR124gZckd5st4YoSuhF1Rebi37qTXKnqYyFCd9KP4vl2cQHbtuVKjfEKrNJxHHCW8thbQ==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.0.4", + "@vitest/utils": "2.1.9", "pathe": "^1.1.2" }, "funding": { @@ -2213,14 +2242,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.4.tgz", - "integrity": "sha512-or6Mzoz/pD7xTvuJMFYEtso1vJo1S5u6zBTinfl+7smGUhqybn6VjzCDMhmTyVOFWwkCMuNjmNNxnyXPgKDoPw==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.4", - "magic-string": "^0.30.10", + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -2228,28 +2257,27 @@ } }, "node_modules/@vitest/spy": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.4.tgz", - "integrity": "sha512-uTXU56TNoYrTohb+6CseP8IqNwlNdtPwEO0AWl+5j7NelS6x0xZZtP0bDWaLvOfUbaYwhhWp1guzXUxkC7mW7Q==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.4.tgz", - "integrity": "sha512-Zc75QuuoJhOBnlo99ZVUkJIuq4Oj0zAkrQ2VzCqNCx6wAwViHEh5Fnp4fiJTE9rA+sAoXRf00Z9xGgfEzV6fzQ==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.4", - "estree-walker": "^3.0.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -2545,9 +2573,9 @@ "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "license": "MIT", "dependencies": { @@ -2800,13 +2828,13 @@ } }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -3004,6 +3032,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -3450,28 +3485,14 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, + "license": "Apache-2.0", "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">=12.0.0" } }, "node_modules/external-editor": { @@ -3695,16 +3716,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -3724,19 +3735,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-tsconfig": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.0.tgz", @@ -3968,16 +3966,6 @@ "node": ">= 14" } }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, "node_modules/i18next": { "version": "23.12.2", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.12.2.tgz", @@ -4240,19 +4228,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -4716,14 +4691,11 @@ "license": "MIT" }, "node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } + "license": "MIT" }, "node_modules/lru-cache": { "version": "5.1.1", @@ -4743,9 +4715,9 @@ "license": "MIT" }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", "dependencies": { @@ -4753,14 +4725,14 @@ } }, "node_modules/magicast": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", - "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.4", - "@babel/types": "^7.24.0", + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, @@ -4819,13 +4791,6 @@ "url": "https://github.com/sindresorhus/memoize?sponsor=1" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4886,19 +4851,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mimic-function": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", @@ -4959,9 +4911,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, @@ -5138,35 +5090,6 @@ "integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==", "dev": true }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/nwsapi": { "version": "2.2.12", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", @@ -5182,22 +5105,6 @@ "node": ">= 0.4" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5978,9 +5885,9 @@ } }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", "dev": true, "license": "MIT" }, @@ -6106,19 +6013,6 @@ "node": ">=4" } }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6225,16 +6119,23 @@ "license": "MIT" }, "node_modules/tinybench": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz", - "integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, "node_modules/tinypool": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", - "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", "dev": true, "license": "MIT", "engines": { @@ -6252,9 +6153,9 @@ } }, "node_modules/tinyspy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", - "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "license": "MIT", "engines": { @@ -6274,16 +6175,6 @@ "node": ">=0.6.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6561,10 +6452,11 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "5.4.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", - "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -6620,16 +6512,16 @@ } }, "node_modules/vite-node": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.4.tgz", - "integrity": "sha512-ZpJVkxcakYtig5iakNeL7N3trufe3M6vGuzYAr4GsbCTwobDeyPJpE4cjDhhPluv8OvQCFzu2LWp6GkoKRITXA==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.5", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", - "tinyrainbow": "^1.2.0", "vite": "^5.0.0" }, "bin": { @@ -6663,30 +6555,31 @@ } }, "node_modules/vitest": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.4.tgz", - "integrity": "sha512-luNLDpfsnxw5QSW4bISPe6tkxVvv5wn2BBs/PuDRkhXZ319doZyLOBr1sjfB5yCEpTiU7xCAdViM8TNVGPwoog==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@vitest/expect": "2.0.4", - "@vitest/pretty-format": "^2.0.4", - "@vitest/runner": "2.0.4", - "@vitest/snapshot": "2.0.4", - "@vitest/spy": "2.0.4", - "@vitest/utils": "2.0.4", - "chai": "^5.1.1", - "debug": "^4.3.5", - "execa": "^8.0.1", - "magic-string": "^0.30.10", + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", - "std-env": "^3.7.0", - "tinybench": "^2.8.0", - "tinypool": "^1.0.0", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.0.4", + "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "bin": { @@ -6701,8 +6594,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.0.4", - "@vitest/ui": "2.0.4", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index 4c05571e55a..94d580a3ec9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.5.2", + "version": "1.5.4", "type": "module", "scripts": { "start": "vite", @@ -33,7 +33,7 @@ "@types/node": "^20.12.13", "@typescript-eslint/eslint-plugin": "^8.0.0-alpha.54", "@typescript-eslint/parser": "^8.0.0-alpha.54", - "@vitest/coverage-istanbul": "^2.0.4", + "@vitest/coverage-istanbul": "^2.1.9", "dependency-cruiser": "^16.3.10", "eslint": "^9.7.0", "eslint-plugin-import-x": "^4.2.1", @@ -45,9 +45,9 @@ "typedoc": "^0.26.4", "typescript": "^5.5.3", "typescript-eslint": "^8.0.0-alpha.54", - "vite": "^5.4.8", + "vite": "^5.4.14", "vite-tsconfig-paths": "^4.3.2", - "vitest": "^2.0.4", + "vitest": "^2.1.9", "vitest-canvas-mock": "^0.3.3" }, "dependencies": { diff --git a/public/audio/bgm/end.mp3 b/public/audio/bgm/end.mp3 index c37973fd9cc..fa33f890dc3 100644 Binary files a/public/audio/bgm/end.mp3 and b/public/audio/bgm/end.mp3 differ diff --git a/public/images/ui/legacy/mystery_egg.png b/public/images/ui/legacy/mystery_egg.png new file mode 100644 index 00000000000..bb117a137b0 Binary files /dev/null and b/public/images/ui/legacy/mystery_egg.png differ diff --git a/public/images/ui/legacy/normal_memory.png b/public/images/ui/legacy/normal_memory.png new file mode 100644 index 00000000000..ddc22d1d4ab Binary files /dev/null and b/public/images/ui/legacy/normal_memory.png differ diff --git a/public/images/ui/legacy/pokedex_summary_bg.png b/public/images/ui/legacy/pokedex_summary_bg.png new file mode 100644 index 00000000000..690df1547c0 Binary files /dev/null and b/public/images/ui/legacy/pokedex_summary_bg.png differ diff --git a/public/images/ui/mystery_egg.png b/public/images/ui/mystery_egg.png new file mode 100644 index 00000000000..bb117a137b0 Binary files /dev/null and b/public/images/ui/mystery_egg.png differ diff --git a/public/images/ui/normal_memory.png b/public/images/ui/normal_memory.png new file mode 100644 index 00000000000..ddc22d1d4ab Binary files /dev/null and b/public/images/ui/normal_memory.png differ diff --git a/public/images/ui/pokedex_summary_bg.png b/public/images/ui/pokedex_summary_bg.png new file mode 100644 index 00000000000..92e70bbee27 Binary files /dev/null and b/public/images/ui/pokedex_summary_bg.png differ diff --git a/public/locales b/public/locales index 2d3765a4f03..5f6fa82c17d 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 2d3765a4f035b4916523bf75b754e153e9d65134 +Subproject commit 5f6fa82c17d5981eaec15f105880ac2b4c99cc8d diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 82ee785896c..9bfa153ef60 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -161,6 +161,7 @@ export default class BattleScene extends SceneBase { public reroll: boolean = false; public shopCursorTarget: number = ShopCursorTarget.REWARDS; public commandCursorMemory: boolean = false; + public dexForDevs: boolean = false; public showMovesetFlyout: boolean = true; public showArenaFlyout: boolean = true; public showTimeOfDayWidget: boolean = true; diff --git a/src/data/balance/egg-moves.ts b/src/data/balance/egg-moves.ts index 4855379f675..35ec6f934a4 100644 --- a/src/data/balance/egg-moves.ts +++ b/src/data/balance/egg-moves.ts @@ -7,10 +7,10 @@ import { Species } from "#enums/species"; export const speciesEggMoves = { [Species.BULBASAUR]: [ Moves.SAPPY_SEED, Moves.MALIGNANT_CHAIN, Moves.EARTH_POWER, Moves.MATCHA_GOTCHA ], [Species.CHARMANDER]: [ Moves.DRAGON_DANCE, Moves.BITTER_BLADE, Moves.EARTH_POWER, Moves.OBLIVION_WING ], - [Species.SQUIRTLE]: [ Moves.FREEZE_DRY, Moves.ARMOR_CANNON, Moves.BOUNCY_BUBBLE, Moves.ORIGIN_PULSE ], + [Species.SQUIRTLE]: [ Moves.FREEZE_DRY, Moves.ARMOR_CANNON, Moves.SHORE_UP, Moves.ORIGIN_PULSE ], [Species.CATERPIE]: [ Moves.SANDSEAR_STORM, Moves.SILK_TRAP, Moves.TWIN_BEAM, Moves.BLEAKWIND_STORM ], [Species.WEEDLE]: [ Moves.THOUSAND_ARROWS, Moves.NOXIOUS_TORQUE, Moves.ATTACK_ORDER, Moves.VICTORY_DANCE ], - [Species.PIDGEY]: [ Moves.WILDBOLT_STORM, Moves.SANDSEAR_STORM, Moves.NASTY_PLOT, Moves.BOOMBURST ], + [Species.PIDGEY]: [ Moves.BLEAKWIND_STORM, Moves.SANDSEAR_STORM, Moves.CALM_MIND, Moves.BOOMBURST ], [Species.RATTATA]: [ Moves.HYPER_FANG, Moves.PSYCHIC_FANGS, Moves.FIRE_FANG, Moves.EXTREME_SPEED ], [Species.SPEAROW]: [ Moves.FLOATY_FALL, Moves.HYPER_DRILL, Moves.TIDY_UP, Moves.TRIPLE_ARROWS ], [Species.EKANS]: [ Moves.NOXIOUS_TORQUE, Moves.DRAGON_DANCE, Moves.SLACK_OFF, Moves.SHED_TAIL ], @@ -34,7 +34,7 @@ export const speciesEggMoves = { [Species.TENTACOOL]: [ Moves.BANEFUL_BUNKER, Moves.MALIGNANT_CHAIN, Moves.BOUNCY_BUBBLE, Moves.STRENGTH_SAP ], [Species.GEODUDE]: [ Moves.FLARE_BLITZ, Moves.HEAD_SMASH, Moves.SHORE_UP, Moves.SHELL_SMASH ], [Species.PONYTA]: [ Moves.HEADLONG_RUSH, Moves.FIRE_LASH, Moves.SWORDS_DANCE, Moves.VOLT_TACKLE ], - [Species.SLOWPOKE]: [ Moves.BOUNCY_BUBBLE, Moves.FROST_BREATH, Moves.SHED_TAIL, Moves.MYSTICAL_POWER ], + [Species.SLOWPOKE]: [ Moves.SPLISHY_SPLASH, Moves.FROST_BREATH, Moves.SHED_TAIL, Moves.MYSTICAL_POWER ], [Species.MAGNEMITE]: [ Moves.PARABOLIC_CHARGE, Moves.FLAMETHROWER, Moves.ICE_BEAM, Moves.THUNDERCLAP ], [Species.FARFETCHD]: [ Moves.IVY_CUDGEL, Moves.TRIPLE_ARROWS, Moves.DRILL_RUN, Moves.VICTORY_DANCE ], [Species.DODUO]: [ Moves.TRIPLE_AXEL, Moves.HYPER_DRILL, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ], @@ -52,7 +52,7 @@ export const speciesEggMoves = { [Species.KOFFING]: [ Moves.SCALD, Moves.RECOVER, Moves.BODY_PRESS, Moves.MALIGNANT_CHAIN ], [Species.RHYHORN]: [ Moves.SHORE_UP, Moves.ICE_HAMMER, Moves.ACCELEROCK, Moves.HEAD_SMASH ], [Species.TANGELA]: [ Moves.NATURES_MADNESS, Moves.SNAP_TRAP, Moves.PARTING_SHOT, Moves.SAPPY_SEED ], - [Species.KANGASKHAN]: [ Moves.POWER_UP_PUNCH, Moves.TRAILBLAZE, Moves.FACADE, Moves.SEISMIC_TOSS ], + [Species.KANGASKHAN]: [ Moves.POWER_UP_PUNCH, Moves.TRAILBLAZE, Moves.COVET, Moves.SEISMIC_TOSS ], [Species.HORSEA]: [ Moves.SNIPE_SHOT, Moves.FROST_BREATH, Moves.SLUDGE_BOMB, Moves.CLANGING_SCALES ], [Species.GOLDEEN]: [ Moves.GLACIAL_LANCE, Moves.SUPERCELL_SLAM, Moves.DRAGON_DANCE, Moves.FISHIOUS_REND ], [Species.STARYU]: [ Moves.CALM_MIND, Moves.BOUNCY_BUBBLE, Moves.MOONBLAST, Moves.MYSTICAL_POWER ], @@ -112,7 +112,7 @@ export const speciesEggMoves = { [Species.REMORAID]: [ Moves.WATER_SHURIKEN, Moves.TAKE_HEART, Moves.SHELL_SIDE_ARM, Moves.BOUNCY_BUBBLE ], [Species.DELIBIRD]: [ Moves.BONEMERANG, Moves.FLOATY_FALL, Moves.VICTORY_DANCE, Moves.GLACIAL_LANCE ], [Species.SKARMORY]: [ Moves.ROOST, Moves.BODY_PRESS, Moves.SPIKY_SHIELD, Moves.BEAK_BLAST ], - [Species.HOUNDOUR]: [ Moves.EARTH_POWER, Moves.THUNDERBOLT, Moves.MOONBLAST, Moves.FIERY_WRATH ], + [Species.HOUNDOUR]: [ Moves.FIERY_WRATH, Moves.THUNDERBOLT, Moves.MOONBLAST, Moves.ARMOR_CANNON ], [Species.PHANPY]: [ Moves.SHORE_UP, Moves.SWORDS_DANCE, Moves.MOUNTAIN_GALE, Moves.COLLISION_COURSE ], [Species.STANTLER]: [ Moves.THUNDEROUS_KICK, Moves.PHOTON_GEYSER, Moves.SWORDS_DANCE, Moves.BOOMBURST ], [Species.SMEARGLE]: [ Moves.CONVERSION, Moves.BURNING_BULWARK, Moves.SALT_CURE, Moves.DARK_VOID ], @@ -132,7 +132,7 @@ export const speciesEggMoves = { [Species.TREECKO]: [ Moves.NASTY_PLOT, Moves.CORE_ENFORCER, Moves.FLAMETHROWER, Moves.SEED_FLARE ], [Species.TORCHIC]: [ Moves.THUNDEROUS_KICK, Moves.ZING_ZAP, Moves.BURNING_BULWARK, Moves.PYRO_BALL ], [Species.MUDKIP]: [ Moves.SHORE_UP, Moves.MOUNTAIN_GALE, Moves.AQUA_STEP, Moves.PRECIPICE_BLADES ], - [Species.POOCHYENA]: [ Moves.JAW_LOCK, Moves.CLOSE_COMBAT, Moves.DIRE_CLAW, Moves.NO_RETREAT ], + [Species.POOCHYENA]: [ Moves.KNOCK_OFF, Moves.CLOSE_COMBAT, Moves.DIRE_CLAW, Moves.VICTORY_DANCE ], [Species.ZIGZAGOON]: [ Moves.EXTREME_SPEED, Moves.NUZZLE, Moves.HIGH_HORSEPOWER, Moves.TIDY_UP ], [Species.WURMPLE]: [ Moves.BATON_PASS, Moves.BLEAKWIND_STORM, Moves.STORED_POWER, Moves.MALIGNANT_CHAIN ], [Species.LOTAD]: [ Moves.REVELATION_DANCE, Moves.APPLE_ACID, Moves.ICE_BEAM, Moves.QUIVER_DANCE ], @@ -185,26 +185,26 @@ export const speciesEggMoves = { [Species.TROPIUS]: [ Moves.STUFF_CHEEKS, Moves.EARTH_POWER, Moves.APPLE_ACID, Moves.SAPPY_SEED ], [Species.ABSOL]: [ Moves.KOWTOW_CLEAVE, Moves.SACRED_SWORD, Moves.PSYBLADE, Moves.BITTER_BLADE ], [Species.WYNAUT]: [ Moves.RECOVER, Moves.SHED_TAIL, Moves.TAUNT, Moves.COMEUPPANCE ], - [Species.SNORUNT]: [ Moves.FREEZY_FROST, Moves.EXTREME_SPEED, Moves.EARTH_POWER, Moves.NO_RETREAT ], + [Species.SNORUNT]: [ Moves.SPARKLY_SWIRL, Moves.NASTY_PLOT, Moves.EARTH_POWER, Moves.BLOOD_MOON ], [Species.SPHEAL]: [ Moves.FLIP_TURN, Moves.FREEZE_DRY, Moves.SLACK_OFF, Moves.STEAM_ERUPTION ], [Species.CLAMPERL]: [ Moves.SHELL_SIDE_ARM, Moves.BOUNCY_BUBBLE, Moves.FREEZE_DRY, Moves.STEAM_ERUPTION ], [Species.RELICANTH]: [ Moves.DRAGON_DANCE, Moves.SHORE_UP, Moves.WAVE_CRASH, Moves.DIAMOND_STORM ], [Species.LUVDISC]: [ Moves.BATON_PASS, Moves.HEART_SWAP, Moves.GLITZY_GLOW, Moves.REVIVAL_BLESSING ], - [Species.BAGON]: [ Moves.FLOATY_FALL, Moves.FIRE_LASH, Moves.DRAGON_DANCE, Moves.DRAGON_DARTS ], + [Species.BAGON]: [ Moves.HEADLONG_RUSH, Moves.FIRE_LASH, Moves.DRAGON_DANCE, Moves.DRAGON_DARTS ], [Species.BELDUM]: [ Moves.HEADLONG_RUSH, Moves.DRAIN_PUNCH, Moves.TRIPLE_AXEL, Moves.SHIFT_GEAR ], [Species.REGIROCK]: [ Moves.STONE_AXE, Moves.BODY_PRESS, Moves.SHORE_UP, Moves.SALT_CURE ], [Species.REGICE]: [ Moves.EARTH_POWER, Moves.TAKE_HEART, Moves.RECOVER, Moves.FREEZE_DRY ], [Species.REGISTEEL]: [ Moves.BODY_PRESS, Moves.SIZZLY_SLIDE, Moves.RECOVER, Moves.GIGATON_HAMMER ], [Species.LATIAS]: [ Moves.CORE_ENFORCER, Moves.FUSION_FLARE, Moves.SPARKLY_SWIRL, Moves.MYSTICAL_POWER ], [Species.LATIOS]: [ Moves.CORE_ENFORCER, Moves.BLUE_FLARE, Moves.NASTY_PLOT, Moves.TACHYON_CUTTER ], - [Species.KYOGRE]: [ Moves.WILDBOLT_STORM, Moves.HURRICANE, Moves.FREEZY_FROST, Moves.BOUNCY_BUBBLE ], + [Species.KYOGRE]: [ Moves.RECOVER, Moves.HURRICANE, Moves.FREEZY_FROST, Moves.WILDBOLT_STORM ], [Species.GROUDON]: [ Moves.STONE_AXE, Moves.SOLAR_BLADE, Moves.MORNING_SUN, Moves.SACRED_FIRE ], [Species.RAYQUAZA]: [ Moves.V_CREATE, Moves.DRAGON_DARTS, Moves.CORE_ENFORCER, Moves.OBLIVION_WING ], [Species.JIRACHI]: [ Moves.TACHYON_CUTTER, Moves.TRIPLE_ARROWS, Moves.ROCK_SLIDE, Moves.SHELL_SMASH ], [Species.DEOXYS]: [ Moves.COLLISION_COURSE, Moves.FUSION_FLARE, Moves.PARTING_SHOT, Moves.LUMINA_CRASH ], [Species.TURTWIG]: [ Moves.SHELL_SMASH, Moves.MIGHTY_CLEAVE, Moves.ICE_SPINNER, Moves.SAPPY_SEED ], - [Species.CHIMCHAR]: [ Moves.FIERY_DANCE, Moves.SECRET_SWORD, Moves.TRIPLE_AXEL, Moves.SACRED_FIRE ], + [Species.CHIMCHAR]: [ Moves.THUNDERBOLT, Moves.SECRET_SWORD, Moves.TRIPLE_AXEL, Moves.SACRED_FIRE ], [Species.PIPLUP]: [ Moves.KINGS_SHIELD, Moves.TACHYON_CUTTER, Moves.FREEZE_DRY, Moves.STEAM_ERUPTION ], [Species.STARLY]: [ Moves.SWORDS_DANCE, Moves.HEAD_CHARGE, Moves.FLARE_BLITZ, Moves.EXTREME_SPEED ], [Species.BIDOOF]: [ Moves.EXTREME_SPEED, Moves.COSMIC_POWER, Moves.POWER_TRIP, Moves.AQUA_STEP ], @@ -215,15 +215,15 @@ export const speciesEggMoves = { [Species.SHIELDON]: [ Moves.SHORE_UP, Moves.BODY_PRESS, Moves.KINGS_SHIELD, Moves.DIAMOND_STORM ], [Species.BURMY]: [ Moves.FIERY_DANCE, Moves.DEFEND_ORDER, Moves.HEAL_ORDER, Moves.SAPPY_SEED ], [Species.COMBEE]: [ Moves.SPORE, Moves.FLOATY_FALL, Moves.KINGS_SHIELD, Moves.VICTORY_DANCE ], - [Species.PACHIRISU]: [ Moves.FREEZY_FROST, Moves.SIZZLY_SLIDE, Moves.SLACK_OFF, Moves.ZIPPY_ZAP ], + [Species.PACHIRISU]: [ Moves.FREEZY_FROST, Moves.SIZZLY_SLIDE, Moves.SLACK_OFF, Moves.THUNDER_CAGE ], [Species.BUIZEL]: [ Moves.JET_PUNCH, Moves.TRIPLE_AXEL, Moves.SUPERCELL_SLAM, Moves.SURGING_STRIKES ], [Species.CHERUBI]: [ Moves.SLEEP_POWDER, Moves.STRENGTH_SAP, Moves.FIRE_LASH, Moves.FLOWER_TRICK ], [Species.SHELLOS]: [ Moves.BOUNCY_BUBBLE, Moves.SCORCHING_SANDS, Moves.FREEZE_DRY, Moves.STEAM_ERUPTION ], - [Species.DRIFLOON]: [ Moves.WILL_O_WISP, Moves.MIND_BLOWN, Moves.CALM_MIND, Moves.OBLIVION_WING ], - [Species.BUNEARY]: [ Moves.TRIPLE_AXEL, Moves.SWORDS_DANCE, Moves.THUNDEROUS_KICK, Moves.MULTI_ATTACK ], + [Species.DRIFLOON]: [ Moves.PSYCHO_SHIFT, Moves.MIND_BLOWN, Moves.CALM_MIND, Moves.OBLIVION_WING ], + [Species.BUNEARY]: [ Moves.TRIPLE_AXEL, Moves.EXTREME_SPEED, Moves.THUNDEROUS_KICK, Moves.SWORDS_DANCE ], [Species.GLAMEOW]: [ Moves.PARTING_SHOT, Moves.HIGH_HORSEPOWER, Moves.SWORDS_DANCE, Moves.EXTREME_SPEED ], [Species.CHINGLING]: [ Moves.BUZZY_BUZZ, Moves.EERIE_SPELL, Moves.TORCH_SONG, Moves.BOOMBURST ], - [Species.STUNKY]: [ Moves.CEASELESS_EDGE, Moves.KNOCK_OFF, Moves.RECOVER, Moves.DIRE_CLAW ], + [Species.STUNKY]: [ Moves.CEASELESS_EDGE, Moves.FIRE_LASH, Moves.RECOVER, Moves.DIRE_CLAW ], [Species.BRONZOR]: [ Moves.RECOVER, Moves.TACHYON_CUTTER, Moves.GLARE, Moves.LUMINA_CRASH ], [Species.BONSLY]: [ Moves.ACCELEROCK, Moves.SWORDS_DANCE, Moves.STRENGTH_SAP, Moves.SAPPY_SEED ], [Species.MIME_JR]: [ Moves.CHILLY_RECEPTION, Moves.MOONBLAST, Moves.FROST_BREATH, Moves.LUMINA_CRASH ], @@ -237,18 +237,18 @@ export const speciesEggMoves = { [Species.SKORUPI]: [ Moves.COIL, Moves.DIRE_CLAW, Moves.CRABHAMMER, Moves.WICKED_BLOW ], [Species.CROAGUNK]: [ Moves.DIRE_CLAW, Moves.ICE_SPINNER, Moves.THUNDEROUS_KICK, Moves.VICTORY_DANCE ], [Species.CARNIVINE]: [ Moves.STRENGTH_SAP, Moves.FIRE_LASH, Moves.COIL, Moves.SAPPY_SEED ], - [Species.FINNEON]: [ Moves.QUIVER_DANCE, Moves.BOUNCY_BUBBLE, Moves.FREEZE_DRY, Moves.ORIGIN_PULSE ], + [Species.FINNEON]: [ Moves.QUIVER_DANCE, Moves.SPLISHY_SPLASH, Moves.FREEZE_DRY, Moves.OBLIVION_WING ], [Species.MANTYKE]: [ Moves.SPLISHY_SPLASH, Moves.FREEZY_FROST, Moves.NASTY_PLOT, Moves.OBLIVION_WING ], [Species.SNOVER]: [ Moves.LANDS_WRATH, Moves.POWDER, Moves.CALM_MIND, Moves.MATCHA_GOTCHA ], [Species.ROTOM]: [ Moves.STRENGTH_SAP, Moves.FIERY_DANCE, Moves.SPLISHY_SPLASH, Moves.ELECTRO_DRIFT ], - [Species.UXIE]: [ Moves.COSMIC_POWER, Moves.SECRET_SWORD, Moves.RECOVER, Moves.SPARKLY_SWIRL ], - [Species.MESPRIT]: [ Moves.TAIL_GLOW, Moves.AURA_SPHERE, Moves.RECOVER, Moves.LUMINA_CRASH ], - [Species.AZELF]: [ Moves.PSYSTRIKE, Moves.ICE_BEAM, Moves.MOONBLAST, Moves.TAIL_GLOW ], + [Species.UXIE]: [ Moves.LUMINA_CRASH, Moves.AURA_SPHERE, Moves.RECOVER, Moves.TAIL_GLOW ], + [Species.MESPRIT]: [ Moves.PHOTON_GEYSER, Moves.AURA_SPHERE, Moves.RECOVER, Moves.TAIL_GLOW ], + [Species.AZELF]: [ Moves.PSYSTRIKE, Moves.AURA_SPHERE, Moves.ICE_BEAM, Moves.TAIL_GLOW ], [Species.DIALGA]: [ Moves.CORE_ENFORCER, Moves.TAKE_HEART, Moves.RECOVER, Moves.MAKE_IT_RAIN ], [Species.PALKIA]: [ Moves.MALIGNANT_CHAIN, Moves.TAKE_HEART, Moves.RECOVER, Moves.ORIGIN_PULSE ], [Species.HEATRAN]: [ Moves.MATCHA_GOTCHA, Moves.RECOVER, Moves.ERUPTION, Moves.TACHYON_CUTTER ], [Species.REGIGIGAS]: [ Moves.SKILL_SWAP, Moves.RECOVER, Moves.EXTREME_SPEED, Moves.GIGATON_HAMMER ], - [Species.GIRATINA]: [ Moves.DRAGON_DANCE, Moves.GLAIVE_RUSH, Moves.RECOVER, Moves.SPECTRAL_THIEF ], + [Species.GIRATINA]: [ Moves.DRAGON_DANCE, Moves.SPECTRAL_THIEF, Moves.RECOVER, Moves.COLLISION_COURSE ], [Species.CRESSELIA]: [ Moves.COSMIC_POWER, Moves.BODY_PRESS, Moves.SIZZLY_SLIDE, Moves.LUMINA_CRASH ], [Species.PHIONE]: [ Moves.BOUNCY_BUBBLE, Moves.FREEZE_DRY, Moves.STORED_POWER, Moves.ORIGIN_PULSE ], [Species.MANAPHY]: [ Moves.BOUNCY_BUBBLE, Moves.FROST_BREATH, Moves.WILDBOLT_STORM, Moves.ORIGIN_PULSE ], @@ -264,14 +264,14 @@ export const speciesEggMoves = { [Species.LILLIPUP]: [ Moves.CLOSE_COMBAT, Moves.BODY_SLAM, Moves.HIGH_HORSEPOWER, Moves.LAST_RESPECTS ], [Species.PURRLOIN]: [ Moves.ENCORE, Moves.OBSTRUCT, Moves.PARTING_SHOT, Moves.WICKED_BLOW ], [Species.PANSAGE]: [ Moves.SWORDS_DANCE, Moves.FIRE_LASH, Moves.EARTHQUAKE, Moves.IVY_CUDGEL ], - [Species.PANSEAR]: [ Moves.NASTY_PLOT, Moves.HYDRO_STEAM, Moves.SCORCHING_SANDS, Moves.TORCH_SONG ], - [Species.PANPOUR]: [ Moves.NASTY_PLOT, Moves.ENERGY_BALL, Moves.EARTH_POWER, Moves.STEAM_ERUPTION ], + [Species.PANSEAR]: [ Moves.NASTY_PLOT, Moves.HYDRO_STEAM, Moves.EARTH_POWER, Moves.ERUPTION ], + [Species.PANPOUR]: [ Moves.NASTY_PLOT, Moves.ENERGY_BALL, Moves.EARTH_POWER, Moves.WATER_SPOUT ], [Species.MUNNA]: [ Moves.COSMIC_POWER, Moves.AURA_SPHERE, Moves.LUNAR_BLESSING, Moves.MYSTICAL_POWER ], [Species.PIDOVE]: [ Moves.GUNK_SHOT, Moves.TIDY_UP, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ], [Species.BLITZLE]: [ Moves.HORN_LEECH, Moves.SWORDS_DANCE, Moves.FLARE_BLITZ, Moves.BOLT_STRIKE ], [Species.ROGGENROLA]: [ Moves.BODY_PRESS, Moves.CURSE, Moves.SHORE_UP, Moves.DIAMOND_STORM ], [Species.WOOBAT]: [ Moves.ESPER_WING, Moves.STORED_POWER, Moves.MYSTICAL_FIRE, Moves.OBLIVION_WING ], - [Species.DRILBUR]: [ Moves.METEOR_MASH, Moves.MOUNTAIN_GALE, Moves.SHIFT_GEAR, Moves.PRECIPICE_BLADES ], + [Species.DRILBUR]: [ Moves.METEOR_MASH, Moves.ICE_SPINNER, Moves.SHIFT_GEAR, Moves.THOUSAND_ARROWS ], [Species.AUDINO]: [ Moves.TAKE_HEART, Moves.MOONBLAST, Moves.WISH, Moves.MATCHA_GOTCHA ], [Species.TIMBURR]: [ Moves.MACH_PUNCH, Moves.DRAIN_PUNCH, Moves.ICE_HAMMER, Moves.DOUBLE_IRON_BASH ], [Species.TYMPOLE]: [ Moves.JET_PUNCH, Moves.HIGH_HORSEPOWER, Moves.BULK_UP, Moves.SURGING_STRIKES ], @@ -298,18 +298,18 @@ export const speciesEggMoves = { [Species.SOLOSIS]: [ Moves.MIST_BALL, Moves.SPEED_SWAP, Moves.FLAMETHROWER, Moves.LIGHT_OF_RUIN ], [Species.DUCKLETT]: [ Moves.SPLISHY_SPLASH, Moves.SANDSEAR_STORM, Moves.WILDBOLT_STORM, Moves.QUIVER_DANCE ], [Species.VANILLITE]: [ Moves.EARTH_POWER, Moves.AURORA_VEIL, Moves.CALM_MIND, Moves.SPARKLY_SWIRL ], - [Species.DEERLING]: [ Moves.TIDY_UP, Moves.FLOWER_TRICK, Moves.BODY_SLAM, Moves.COMBAT_TORQUE ], + [Species.DEERLING]: [ Moves.TIDY_UP, Moves.HEADBUTT, Moves.COMBAT_TORQUE, Moves.FLOWER_TRICK ], [Species.EMOLGA]: [ Moves.ICICLE_CRASH, Moves.ZING_ZAP, Moves.FLOATY_FALL, Moves.ELECTRIFY ], [Species.KARRABLAST]: [ Moves.LEECH_LIFE, Moves.BITTER_BLADE, Moves.OBSTRUCT, Moves.DOUBLE_IRON_BASH ], [Species.FOONGUS]: [ Moves.POLLEN_PUFF, Moves.PARTING_SHOT, Moves.FOUL_PLAY, Moves.SAPPY_SEED ], [Species.FRILLISH]: [ Moves.CALM_MIND, Moves.BUZZY_BUZZ, Moves.FREEZE_DRY, Moves.STEAM_ERUPTION ], [Species.ALOMOMOLA]: [ Moves.FLIP_TURN, Moves.HEART_SWAP, Moves.GLITZY_GLOW, Moves.REVIVAL_BLESSING ], [Species.JOLTIK]: [ Moves.WILDBOLT_STORM, Moves.PARABOLIC_CHARGE, Moves.EARTH_POWER, Moves.QUIVER_DANCE ], - [Species.FERROSEED]: [ Moves.SYNTHESIS, Moves.COMBAT_TORQUE, Moves.SPIKY_SHIELD, Moves.SAPPY_SEED ], + [Species.FERROSEED]: [ Moves.SYNTHESIS, Moves.CEASELESS_EDGE, Moves.SPIKY_SHIELD, Moves.SAPPY_SEED ], [Species.KLINK]: [ Moves.TRIPLE_AXEL, Moves.HIGH_HORSEPOWER, Moves.RECOVER, Moves.AURA_WHEEL ], [Species.TYNAMO]: [ Moves.SCALD, Moves.STRENGTH_SAP, Moves.FIRE_LASH, Moves.AURA_WHEEL ], [Species.ELGYEM]: [ Moves.THUNDERCLAP, Moves.BADDY_BAD, Moves.AURA_SPHERE, Moves.PHOTON_GEYSER ], - [Species.LITWICK]: [ Moves.PARTING_SHOT, Moves.EARTH_POWER, Moves.MOONBLAST, Moves.TORCH_SONG ], + [Species.LITWICK]: [ Moves.GIGA_DRAIN, Moves.EARTH_POWER, Moves.MOONBLAST, Moves.TORCH_SONG ], [Species.AXEW]: [ Moves.STONE_AXE, Moves.DIRE_CLAW, Moves.BITTER_BLADE, Moves.GLAIVE_RUSH ], [Species.CUBCHOO]: [ Moves.MOUNTAIN_GALE, Moves.AQUA_STEP, Moves.ICE_SHARD, Moves.COLLISION_COURSE ], [Species.CRYOGONAL]: [ Moves.FREEZING_GLARE, Moves.AURORA_VEIL, Moves.NASTY_PLOT, Moves.ORIGIN_PULSE ], @@ -325,14 +325,14 @@ export const speciesEggMoves = { [Species.HEATMOR]: [ Moves.EARTH_POWER, Moves.OVERHEAT, Moves.THUNDERBOLT, Moves.V_CREATE ], [Species.DURANT]: [ Moves.HIGH_HORSEPOWER, Moves.FIRST_IMPRESSION, Moves.SWORDS_DANCE, Moves.BEHEMOTH_BASH ], [Species.DEINO]: [ Moves.FIERY_WRATH, Moves.ESPER_WING, Moves.SLUDGE_BOMB, Moves.FICKLE_BEAM ], - [Species.LARVESTA]: [ Moves.THUNDERBOLT, Moves.MAGMA_STORM, Moves.EARTH_POWER, Moves.MATCHA_GOTCHA ], + [Species.LARVESTA]: [ Moves.THUNDERBOLT, Moves.DAZZLING_GLEAM, Moves.EARTH_POWER, Moves.HYDRO_STEAM ], [Species.COBALION]: [ Moves.BEHEMOTH_BLADE, Moves.MIGHTY_CLEAVE, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ], [Species.TERRAKION]: [ Moves.MIGHTY_CLEAVE, Moves.HEADLONG_RUSH, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ], - [Species.VIRIZION]: [ Moves.PSYBLADE, Moves.SAPPY_SEED, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ], + [Species.VIRIZION]: [ Moves.SAPPY_SEED, Moves.PSYBLADE, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ], [Species.TORNADUS]: [ Moves.SANDSEAR_STORM, Moves.PARTING_SHOT, Moves.SPLISHY_SPLASH, Moves.OBLIVION_WING ], [Species.THUNDURUS]: [ Moves.SANDSEAR_STORM, Moves.HURRICANE, Moves.FROST_BREATH, Moves.ELECTRO_SHOT ], [Species.RESHIRAM]: [ Moves.ENERGY_BALL, Moves.TAKE_HEART, Moves.FICKLE_BEAM, Moves.ERUPTION ], - [Species.ZEKROM]: [ Moves.TRIPLE_AXEL, Moves.THUNDEROUS_KICK, Moves.DRAGON_HAMMER, Moves.BOLT_BEAK ], + [Species.ZEKROM]: [ Moves.TRIPLE_AXEL, Moves.THUNDEROUS_KICK, Moves.DRAGON_HAMMER, Moves.DRAGON_ENERGY ], [Species.LANDORUS]: [ Moves.STONE_AXE, Moves.FLOATY_FALL, Moves.ROOST, Moves.BLEAKWIND_STORM ], [Species.KYUREM]: [ Moves.DRAGON_DARTS, Moves.GLACIAL_LANCE, Moves.NO_RETREAT, Moves.DRAGON_ENERGY ], [Species.KELDEO]: [ Moves.BOUNCY_BUBBLE, Moves.THUNDERBOLT, Moves.ICE_BEAM, Moves.STEAM_ERUPTION ], @@ -366,35 +366,35 @@ export const speciesEggMoves = { [Species.CARBINK]: [ Moves.BODY_PRESS, Moves.SHORE_UP, Moves.SPARKLY_SWIRL, Moves.DIAMOND_STORM ], [Species.GOOMY]: [ Moves.DRAGON_HAMMER, Moves.RECOVER, Moves.CALM_MIND, Moves.MAKE_IT_RAIN ], [Species.KLEFKI]: [ Moves.HEAL_BELL, Moves.ENCORE, Moves.INSTRUCT, Moves.TOPSY_TURVY ], - [Species.PHANTUMP]: [ Moves.RAGE_FIST, Moves.SLEEP_POWDER, Moves.SYNTHESIS, Moves.SAPPY_SEED ], + [Species.PHANTUMP]: [ Moves.RAGE_FIST, Moves.SLEEP_POWDER, Moves.BULK_UP, Moves.SAPPY_SEED ], [Species.PUMPKABOO]: [ Moves.SPIRIT_SHACKLE, Moves.FIRE_LASH, Moves.DIRE_CLAW, Moves.SAPPY_SEED ], [Species.BERGMITE]: [ Moves.STONE_AXE, Moves.METAL_BURST, Moves.BODY_PRESS, Moves.GLACIAL_LANCE ], [Species.NOIBAT]: [ Moves.AEROBLAST, Moves.OVERDRIVE, Moves.NASTY_PLOT, Moves.CLANGING_SCALES ], [Species.XERNEAS]: [ Moves.EARTH_POWER, Moves.SPRINGTIDE_STORM, Moves.STRENGTH_SAP, Moves.TAIL_GLOW ], - [Species.YVELTAL]: [ Moves.SHELL_SIDE_ARM, Moves.POWER_TRIP, Moves.FIERY_WRATH, Moves.CLANGOROUS_SOUL ], + [Species.YVELTAL]: [ Moves.SLUDGE_WAVE, Moves.POWER_TRIP, Moves.FIERY_WRATH, Moves.CLANGOROUS_SOUL ], [Species.ZYGARDE]: [ Moves.DRAGON_DARTS, Moves.HEAL_ORDER, Moves.CLANGOROUS_SOUL, Moves.DOUBLE_IRON_BASH ], [Species.DIANCIE]: [ Moves.MAGICAL_TORQUE, Moves.FIERY_DANCE, Moves.SHORE_UP, Moves.GEOMANCY ], [Species.HOOPA]: [ Moves.PHOTON_GEYSER, Moves.SECRET_SWORD, Moves.FIERY_WRATH, Moves.SHELL_SMASH ], - [Species.VOLCANION]: [ Moves.HYDRO_STEAM, Moves.CALM_MIND, Moves.ENERGY_BALL, Moves.MAGMA_STORM ], + [Species.VOLCANION]: [ Moves.HYDRO_STEAM, Moves.CALM_MIND, Moves.SEARING_SHOT, Moves.THUNDERCLAP ], [Species.ETERNAL_FLOETTE]: [ Moves.MIND_BLOWN, Moves.CHLOROBLAST, Moves.LUSTER_PURGE, Moves.QUIVER_DANCE ], [Species.ROWLET]: [ Moves.THOUSAND_ARROWS, Moves.SHADOW_BONE, Moves.FIRST_IMPRESSION, Moves.VICTORY_DANCE ], [Species.LITTEN]: [ Moves.SUCKER_PUNCH, Moves.PARTING_SHOT, Moves.SLACK_OFF, Moves.SACRED_FIRE ], - [Species.POPPLIO]: [ Moves.PSYCHIC_NOISE, Moves.BOUNCY_BUBBLE, Moves.OVERDRIVE, Moves.TORCH_SONG ], + [Species.POPPLIO]: [ Moves.PSYCHIC_NOISE, Moves.MOONLIGHT, Moves.OVERDRIVE, Moves.TORCH_SONG ], [Species.PIKIPEK]: [ Moves.DUAL_WINGBEAT, Moves.BONE_RUSH, Moves.BURNING_BULWARK, Moves.POPULATION_BOMB ], [Species.YUNGOOS]: [ Moves.EXTREME_SPEED, Moves.KNOCK_OFF, Moves.TIDY_UP, Moves.MULTI_ATTACK ], [Species.GRUBBIN]: [ Moves.ICE_BEAM, Moves.EARTH_POWER, Moves.THUNDERCLAP, Moves.QUIVER_DANCE ], - [Species.CRABRAWLER]: [ Moves.JET_PUNCH, Moves.SHORE_UP, Moves.SUCKER_PUNCH, Moves.SURGING_STRIKES ], + [Species.CRABRAWLER]: [ Moves.JET_PUNCH, Moves.SHORE_UP, Moves.MACH_PUNCH, Moves.SURGING_STRIKES ], [Species.ORICORIO]: [ Moves.QUIVER_DANCE, Moves.FIERY_DANCE, Moves.THUNDERCLAP, Moves.OBLIVION_WING ], [Species.CUTIEFLY]: [ Moves.STICKY_WEB, Moves.SLEEP_POWDER, Moves.HEAT_WAVE, Moves.SPARKLY_SWIRL ], [Species.ROCKRUFF]: [ Moves.HIGH_HORSEPOWER, Moves.TIDY_UP, Moves.ICE_SPINNER, Moves.MIGHTY_CLEAVE ], [Species.WISHIWASHI]: [ Moves.HEAL_ORDER, Moves.FREEZE_DRY, Moves.WATER_SHURIKEN, Moves.TAIL_GLOW ], [Species.MAREANIE]: [ Moves.CEASELESS_EDGE, Moves.SIZZLY_SLIDE, Moves.BODY_PRESS, Moves.LEECH_SEED ], [Species.MUDBRAY]: [ Moves.BODY_PRESS, Moves.YAWN, Moves.SHORE_UP, Moves.THOUSAND_WAVES ], - [Species.DEWPIDER]: [ Moves.JET_PUNCH, Moves.SILK_TRAP, Moves.SWORDS_DANCE, Moves.AQUA_STEP ], + [Species.DEWPIDER]: [ Moves.AQUA_STEP, Moves.SILK_TRAP, Moves.SWORDS_DANCE, Moves.JET_PUNCH ], [Species.FOMANTIS]: [ Moves.SUPERPOWER, Moves.HEADLONG_RUSH, Moves.ICE_HAMMER, Moves.BITTER_BLADE ], [Species.MORELULL]: [ Moves.CALM_MIND, Moves.SAPPY_SEED, Moves.DRAINING_KISS, Moves.MATCHA_GOTCHA ], - [Species.SALANDIT]: [ Moves.SCALD, Moves.SLUDGE_WAVE, Moves.CORE_ENFORCER, Moves.ERUPTION ], + [Species.SALANDIT]: [ Moves.SCALD, Moves.MALIGNANT_CHAIN, Moves.CORE_ENFORCER, Moves.ERUPTION ], [Species.STUFFUL]: [ Moves.DRAIN_PUNCH, Moves.METEOR_MASH, Moves.TRIPLE_AXEL, Moves.RAGE_FIST ], [Species.BOUNSWEET]: [ Moves.TRIPLE_AXEL, Moves.AQUA_STEP, Moves.THUNDEROUS_KICK, Moves.SAPPY_SEED ], [Species.COMFEY]: [ Moves.REVIVAL_BLESSING, Moves.TAKE_HEART, Moves.STRENGTH_SAP, Moves.MATCHA_GOTCHA ], @@ -416,25 +416,25 @@ export const speciesEggMoves = { [Species.TAPU_KOKO]: [ Moves.MAGICAL_TORQUE, Moves.TRIPLE_AXEL, Moves.SWORDS_DANCE, Moves.BOLT_STRIKE ], [Species.TAPU_LELE]: [ Moves.MOONLIGHT, Moves.NASTY_PLOT, Moves.HEAT_WAVE, Moves.EXPANDING_FORCE ], [Species.TAPU_BULU]: [ Moves.SAPPY_SEED, Moves.DRAIN_PUNCH, Moves.MAGICAL_TORQUE, Moves.VICTORY_DANCE ], - [Species.TAPU_FINI]: [ Moves.AURA_SPHERE, Moves.EARTH_POWER, Moves.RECOVER, Moves.QUIVER_DANCE ], + [Species.TAPU_FINI]: [ Moves.SPRINGTIDE_STORM, Moves.EARTH_POWER, Moves.RECOVER, Moves.QUIVER_DANCE ], [Species.COSMOG]: [ Moves.PHOTON_GEYSER, Moves.PRECIPICE_BLADES, Moves.SACRED_FIRE, Moves.ASTRAL_BARRAGE ], [Species.NIHILEGO]: [ Moves.STRENGTH_SAP, Moves.MALIGNANT_CHAIN, Moves.EARTH_POWER, Moves.QUIVER_DANCE ], [Species.BUZZWOLE]: [ Moves.FIRST_IMPRESSION, Moves.COMBAT_TORQUE, Moves.ROCK_BLAST, Moves.DOUBLE_IRON_BASH ], [Species.PHEROMOSA]: [ Moves.SECRET_SWORD, Moves.MAKE_IT_RAIN, Moves.ATTACK_ORDER, Moves.DIAMOND_STORM ], [Species.XURKITREE]: [ Moves.FLAMETHROWER, Moves.GIGA_DRAIN, Moves.TAIL_GLOW, Moves.THUNDERCLAP ], - [Species.CELESTEELA]: [ Moves.RECOVER, Moves.BUZZY_BUZZ, Moves.SANDSEAR_STORM, Moves.OBLIVION_WING ], + [Species.CELESTEELA]: [ Moves.RECOVER, Moves.BUZZY_BUZZ, Moves.EARTH_POWER, Moves.OBLIVION_WING ], [Species.KARTANA]: [ Moves.MIGHTY_CLEAVE, Moves.PSYBLADE, Moves.BITTER_BLADE, Moves.BEHEMOTH_BLADE ], [Species.GUZZLORD]: [ Moves.SUCKER_PUNCH, Moves.COMEUPPANCE, Moves.SLACK_OFF, Moves.SHED_TAIL ], [Species.NECROZMA]: [ Moves.DYNAMAX_CANNON, Moves.SACRED_FIRE, Moves.ASTRAL_BARRAGE, Moves.CLANGOROUS_SOUL ], [Species.MAGEARNA]: [ Moves.STRENGTH_SAP, Moves.EARTH_POWER, Moves.MOONBLAST, Moves.MAKE_IT_RAIN ], - [Species.MARSHADOW]: [ Moves.POWER_UP_PUNCH, Moves.TRIPLE_AXEL, Moves.METEOR_MASH, Moves.STORM_THROW ], + [Species.MARSHADOW]: [ Moves.POWER_UP_PUNCH, Moves.BONEMERANG, Moves.METEOR_MASH, Moves.TRIPLE_AXEL ], [Species.POIPOLE]: [ Moves.MALIGNANT_CHAIN, Moves.ICE_BEAM, Moves.ARMOR_CANNON, Moves.CLANGING_SCALES ], [Species.STAKATAKA]: [ Moves.HEAVY_SLAM, Moves.SHORE_UP, Moves.CURSE, Moves.SALT_CURE ], [Species.BLACEPHALON]: [ Moves.STEEL_BEAM, Moves.MOONBLAST, Moves.CHLOROBLAST, Moves.MOONGEIST_BEAM ], - [Species.ZERAORA]: [ Moves.SWORDS_DANCE, Moves.TRIPLE_AXEL, Moves.BOLT_STRIKE, Moves.PYRO_BALL ], + [Species.ZERAORA]: [ Moves.SWORDS_DANCE, Moves.U_TURN, Moves.COLLISION_COURSE, Moves.TRIPLE_AXEL ], [Species.MELTAN]: [ Moves.BULLET_PUNCH, Moves.DRAIN_PUNCH, Moves.BULK_UP, Moves.PLASMA_FISTS ], [Species.ALOLA_RATTATA]: [ Moves.FALSE_SURRENDER, Moves.PSYCHIC_FANGS, Moves.COIL, Moves.EXTREME_SPEED ], - [Species.ALOLA_SANDSHREW]: [ Moves.SPIKY_SHIELD, Moves.AQUA_CUTTER, Moves.SHIFT_GEAR, Moves.GLACIAL_LANCE ], + [Species.ALOLA_SANDSHREW]: [ Moves.SPIKY_SHIELD, Moves.LIQUIDATION, Moves.SHIFT_GEAR, Moves.GLACIAL_LANCE ], [Species.ALOLA_VULPIX]: [ Moves.MOONBLAST, Moves.PARTING_SHOT, Moves.EARTH_POWER, Moves.REVIVAL_BLESSING ], [Species.ALOLA_DIGLETT]: [ Moves.THOUSAND_WAVES, Moves.SWORDS_DANCE, Moves.TRIPLE_DIVE, Moves.MOUNTAIN_GALE ], [Species.ALOLA_MEOWTH]: [ Moves.BADDY_BAD, Moves.BUZZY_BUZZ, Moves.PARTING_SHOT, Moves.MAKE_IT_RAIN ], @@ -449,22 +449,22 @@ export const speciesEggMoves = { [Species.BLIPBUG]: [ Moves.HEAL_ORDER, Moves.LUSTER_PURGE, Moves.SLEEP_POWDER, Moves.TAIL_GLOW ], [Species.NICKIT]: [ Moves.BADDY_BAD, Moves.FLAMETHROWER, Moves.SPARKLY_SWIRL, Moves.MAKE_IT_RAIN ], [Species.GOSSIFLEUR]: [ Moves.PARTING_SHOT, Moves.STRENGTH_SAP, Moves.SAPPY_SEED, Moves.SEED_FLARE ], - [Species.WOOLOO]: [ Moves.PSYSHIELD_BASH, Moves.MILK_DRINK, Moves.BODY_PRESS, Moves.MULTI_ATTACK ], + [Species.WOOLOO]: [ Moves.NUZZLE, Moves.MILK_DRINK, Moves.BODY_PRESS, Moves.MULTI_ATTACK ], [Species.CHEWTLE]: [ Moves.ICE_FANG, Moves.PSYCHIC_FANGS, Moves.SHELL_SMASH, Moves.MIGHTY_CLEAVE ], [Species.YAMPER]: [ Moves.ICE_FANG, Moves.SWORDS_DANCE, Moves.THUNDER_FANG, Moves.BOLT_STRIKE ], [Species.ROLYCOLY]: [ Moves.BITTER_BLADE, Moves.BODY_PRESS, Moves.BULK_UP, Moves.DIAMOND_STORM ], [Species.APPLIN]: [ Moves.CORE_ENFORCER, Moves.DRAGON_HAMMER, Moves.FLOWER_TRICK, Moves.MATCHA_GOTCHA ], [Species.SILICOBRA]: [ Moves.SHORE_UP, Moves.SHED_TAIL, Moves.MOUNTAIN_GALE, Moves.THOUSAND_ARROWS ], - [Species.CRAMORANT]: [ Moves.APPLE_ACID, Moves.SURF, Moves.SCORCHING_SANDS, Moves.OBLIVION_WING ], + [Species.CRAMORANT]: [ Moves.APPLE_ACID, Moves.SURF, Moves.BOLT_BEAK, Moves.OBLIVION_WING ], [Species.ARROKUDA]: [ Moves.SUPERCELL_SLAM, Moves.TRIPLE_DIVE, Moves.ICE_SPINNER, Moves.SWORDS_DANCE ], [Species.TOXEL]: [ Moves.NASTY_PLOT, Moves.BUG_BUZZ, Moves.SPARKLING_ARIA, Moves.TORCH_SONG ], [Species.SIZZLIPEDE]: [ Moves.BURNING_BULWARK, Moves.ZING_ZAP, Moves.FIRST_IMPRESSION, Moves.BITTER_BLADE ], [Species.CLOBBOPUS]: [ Moves.STORM_THROW, Moves.JET_PUNCH, Moves.MACH_PUNCH, Moves.SURGING_STRIKES ], - [Species.SINISTEA]: [ Moves.SCALD, Moves.TAKE_HEART, Moves.SPARKLY_SWIRL, Moves.MATCHA_GOTCHA ], + [Species.SINISTEA]: [ Moves.SPLISHY_SPLASH, Moves.MATCHA_GOTCHA, Moves.DRAINING_KISS, Moves.MOONGEIST_BEAM ], [Species.HATENNA]: [ Moves.RECOVER, Moves.MOONBLAST, Moves.BUZZY_BUZZ, Moves.TORCH_SONG ], - [Species.IMPIDIMP]: [ Moves.ENCORE, Moves.PARTING_SHOT, Moves.TOPSY_TURVY, Moves.WICKED_BLOW ], + [Species.IMPIDIMP]: [ Moves.SLACK_OFF, Moves.PARTING_SHOT, Moves.OCTOLOCK, Moves.WICKED_BLOW ], [Species.MILCERY]: [ Moves.MOONBLAST, Moves.CHILLY_RECEPTION, Moves.EARTH_POWER, Moves.GEOMANCY ], - [Species.FALINKS]: [ Moves.COMBAT_TORQUE, Moves.PSYSHIELD_BASH, Moves.HEAL_ORDER, Moves.POPULATION_BOMB ], + [Species.FALINKS]: [ Moves.BATON_PASS, Moves.POWER_TRIP, Moves.HEAL_ORDER, Moves.COMBAT_TORQUE ], [Species.PINCURCHIN]: [ Moves.TRICK_ROOM, Moves.VOLT_SWITCH, Moves.STRENGTH_SAP, Moves.THUNDERCLAP ], [Species.SNOM]: [ Moves.FROST_BREATH, Moves.HEAL_ORDER, Moves.EARTH_POWER, Moves.SPORE ], [Species.STONJOURNER]: [ Moves.BODY_PRESS, Moves.HELPING_HAND, Moves.ACCELEROCK, Moves.DIAMOND_STORM ], @@ -484,9 +484,9 @@ export const speciesEggMoves = { [Species.KUBFU]: [ Moves.METEOR_MASH, Moves.DRAIN_PUNCH, Moves.JET_PUNCH, Moves.DRAGON_DANCE ], [Species.ZARUDE]: [ Moves.SAPPY_SEED, Moves.MIGHTY_CLEAVE, Moves.WICKED_BLOW, Moves.VICTORY_DANCE ], [Species.REGIELEKI]: [ Moves.NASTY_PLOT, Moves.ICE_BEAM, Moves.EARTH_POWER, Moves.ELECTRO_DRIFT ], - [Species.REGIDRAGO]: [ Moves.METEOR_MASH, Moves.FLAMETHROWER, Moves.TAKE_HEART, Moves.DRAGON_DARTS ], + [Species.REGIDRAGO]: [ Moves.SHELL_SIDE_ARM, Moves.FLAMETHROWER, Moves.TAKE_HEART, Moves.DRAGON_DARTS ], [Species.GLASTRIER]: [ Moves.SPEED_SWAP, Moves.SLACK_OFF, Moves.HIGH_HORSEPOWER, Moves.GLACIAL_LANCE ], - [Species.SPECTRIER]: [ Moves.EARTH_POWER, Moves.PARTING_SHOT, Moves.AURA_SPHERE, Moves.ASTRAL_BARRAGE ], + [Species.SPECTRIER]: [ Moves.EARTH_POWER, Moves.MOONLIGHT, Moves.AURA_SPHERE, Moves.ASTRAL_BARRAGE ], [Species.CALYREX]: [ Moves.SAPPY_SEED, Moves.RECOVER, Moves.SECRET_SWORD, Moves.PHOTON_GEYSER ], [Species.ENAMORUS]: [ Moves.AEROBLAST, Moves.THOUSAND_ARROWS, Moves.STORED_POWER, Moves.FLEUR_CANNON ], [Species.GALAR_MEOWTH]: [ Moves.LIQUIDATION, Moves.HORN_LEECH, Moves.BULLET_PUNCH, Moves.BEHEMOTH_BASH ], @@ -494,7 +494,7 @@ export const speciesEggMoves = { [Species.GALAR_SLOWPOKE]: [ Moves.SHED_TAIL, Moves.BADDY_BAD, Moves.MOONBLAST, Moves.PHOTON_GEYSER ], [Species.GALAR_FARFETCHD]: [ Moves.ROOST, Moves.SACRED_SWORD, Moves.KINGS_SHIELD, Moves.BEHEMOTH_BLADE ], [Species.GALAR_ARTICUNO]: [ Moves.SECRET_SWORD, Moves.NIGHT_DAZE, Moves.ICE_BEAM, Moves.OBLIVION_WING ], - [Species.GALAR_ZAPDOS]: [ Moves.TIDY_UP, Moves.FLOATY_FALL, Moves.ROOST, Moves.BOLT_BEAK ], + [Species.GALAR_ZAPDOS]: [ Moves.POISON_JAB, Moves.FLOATY_FALL, Moves.ROOST, Moves.BOLT_BEAK ], [Species.GALAR_MOLTRES]: [ Moves.ROOST, Moves.SLUDGE_BOMB, Moves.FLAMETHROWER, Moves.OBLIVION_WING ], [Species.GALAR_CORSOLA]: [ Moves.SHELL_SMASH, Moves.AURA_SPHERE, Moves.INFERNAL_PARADE, Moves.ASTRAL_BARRAGE ], [Species.GALAR_ZIGZAGOON]: [ Moves.CEASELESS_EDGE, Moves.FACADE, Moves.PARTING_SHOT, Moves.EXTREME_SPEED ], @@ -510,7 +510,7 @@ export const speciesEggMoves = { [Species.SPRIGATITO]: [ Moves.FIRE_LASH, Moves.TRIPLE_AXEL, Moves.SUCKER_PUNCH, Moves.WICKED_BLOW ], [Species.FUECOCO]: [ Moves.ALLURING_VOICE, Moves.SLACK_OFF, Moves.OVERDRIVE, Moves.MOONGEIST_BEAM ], [Species.QUAXLY]: [ Moves.DRAGON_DANCE, Moves.TRIPLE_AXEL, Moves.POWER_TRIP, Moves.THUNDEROUS_KICK ], - [Species.LECHONK]: [ Moves.MILK_DRINK, Moves.PSYSHIELD_BASH, Moves.FILLET_AWAY, Moves.MULTI_ATTACK ], + [Species.LECHONK]: [ Moves.MILK_DRINK, Moves.PSYSHIELD_BASH, Moves.BLAZING_TORQUE, Moves.FILLET_AWAY ], [Species.TAROUNTULA]: [ Moves.STONE_AXE, Moves.LEECH_LIFE, Moves.THIEF, Moves.SPORE ], [Species.NYMBLE]: [ Moves.KNOCK_OFF, Moves.FELL_STINGER, Moves.ATTACK_ORDER, Moves.WICKED_BLOW ], [Species.PAWMI]: [ Moves.DRAIN_PUNCH, Moves.METEOR_MASH, Moves.JET_PUNCH, Moves.PLASMA_FISTS ], @@ -522,13 +522,13 @@ export const speciesEggMoves = { [Species.CHARCADET]: [ Moves.SACRED_SWORD, Moves.PHOTON_GEYSER, Moves.MOONBLAST, Moves.SPECTRAL_THIEF ], [Species.TADBULB]: [ Moves.PARABOLIC_CHARGE, Moves.SCALD, Moves.EARTH_POWER, Moves.ELECTRO_SHOT ], [Species.WATTREL]: [ Moves.NASTY_PLOT, Moves.SPLISHY_SPLASH, Moves.SANDSEAR_STORM, Moves.ELECTRO_SHOT ], - [Species.MASCHIFF]: [ Moves.PARTING_SHOT, Moves.CLOSE_COMBAT, Moves.PSYCHIC_FANGS, Moves.NO_RETREAT ], + [Species.MASCHIFF]: [ Moves.PARTING_SHOT, Moves.COMBAT_TORQUE, Moves.PSYCHIC_FANGS, Moves.NO_RETREAT ], [Species.SHROODLE]: [ Moves.GASTRO_ACID, Moves.PARTING_SHOT, Moves.TOXIC, Moves.SKETCH ], [Species.BRAMBLIN]: [ Moves.TAILWIND, Moves.STRENGTH_SAP, Moves.FLOWER_TRICK, Moves.LAST_RESPECTS ], [Species.TOEDSCOOL]: [ Moves.STRENGTH_SAP, Moves.TOPSY_TURVY, Moves.SAPPY_SEED, Moves.TAIL_GLOW ], [Species.KLAWF]: [ Moves.CRABHAMMER, Moves.SHORE_UP, Moves.MIGHTY_CLEAVE, Moves.SHELL_SMASH ], [Species.CAPSAKID]: [ Moves.STRENGTH_SAP, Moves.APPLE_ACID, Moves.FROST_BREATH, Moves.TORCH_SONG ], - [Species.RELLOR]: [ Moves.HEAL_BLOCK, Moves.RECOVER, Moves.HEAT_WAVE, Moves.LUMINA_CRASH ], + [Species.RELLOR]: [ Moves.HEAL_BLOCK, Moves.RECOVER, Moves.MAGIC_POWDER, Moves.LUMINA_CRASH ], [Species.FLITTLE]: [ Moves.COSMIC_POWER, Moves.AURA_SPHERE, Moves.ROOST, Moves.FIERY_DANCE ], [Species.TINKATINK]: [ Moves.MAGICAL_TORQUE, Moves.PYRO_BALL, Moves.IVY_CUDGEL, Moves.SHIFT_GEAR ], [Species.WIGLETT]: [ Moves.SHELL_SMASH, Moves.ICICLE_CRASH, Moves.SEED_BOMB, Moves.SURGING_STRIKES ], @@ -537,11 +537,11 @@ export const speciesEggMoves = { [Species.VAROOM]: [ Moves.COMBAT_TORQUE, Moves.U_TURN, Moves.BLAZING_TORQUE, Moves.NOXIOUS_TORQUE ], [Species.CYCLIZAR]: [ Moves.PARTING_SHOT, Moves.FIRE_LASH, Moves.MAGICAL_TORQUE, Moves.GLAIVE_RUSH ], [Species.ORTHWORM]: [ Moves.SIZZLY_SLIDE, Moves.COIL, Moves.BODY_PRESS, Moves.SHORE_UP ], - [Species.GLIMMET]: [ Moves.CALM_MIND, Moves.EARTH_POWER, Moves.FIERY_DANCE, Moves.MALIGNANT_CHAIN ], + [Species.GLIMMET]: [ Moves.CALM_MIND, Moves.GIGA_DRAIN, Moves.FIERY_DANCE, Moves.MALIGNANT_CHAIN ], [Species.GREAVARD]: [ Moves.SHADOW_BONE, Moves.YAWN, Moves.SHORE_UP, Moves.COLLISION_COURSE ], [Species.FLAMIGO]: [ Moves.THUNDEROUS_KICK, Moves.TRIPLE_AXEL, Moves.FLOATY_FALL, Moves.VICTORY_DANCE ], - [Species.CETODDLE]: [ Moves.MOUNTAIN_GALE, Moves.HIGH_HORSEPOWER, Moves.SLACK_OFF, Moves.DRAGON_DANCE ], - [Species.VELUZA]: [ Moves.PSYBLADE, Moves.FLIP_TURN, Moves.ICE_SPINNER, Moves.BITTER_BLADE ], + [Species.CETODDLE]: [ Moves.ZING_ZAP, Moves.HIGH_HORSEPOWER, Moves.SLACK_OFF, Moves.DRAGON_DANCE ], + [Species.VELUZA]: [ Moves.PSYBLADE, Moves.LEAF_BLADE, Moves.CEASELESS_EDGE, Moves.BITTER_BLADE ], [Species.DONDOZO]: [ Moves.SOFT_BOILED, Moves.SIZZLY_SLIDE, Moves.BREAKING_SWIPE, Moves.SALT_CURE ], [Species.TATSUGIRI]: [ Moves.SLUDGE_BOMB, Moves.FILLET_AWAY, Moves.CORE_ENFORCER, Moves.STEAM_ERUPTION ], [Species.GREAT_TUSK]: [ Moves.STONE_AXE, Moves.MORNING_SUN, Moves.COLLISION_COURSE, Moves.SHIFT_GEAR ], @@ -551,7 +551,7 @@ export const speciesEggMoves = { [Species.SLITHER_WING]: [ Moves.MIGHTY_CLEAVE, Moves.THUNDEROUS_KICK, Moves.FIRE_LASH, Moves.VICTORY_DANCE ], [Species.SANDY_SHOCKS]: [ Moves.MORNING_SUN, Moves.ICE_BEAM, Moves.NASTY_PLOT, Moves.THUNDERCLAP ], [Species.IRON_TREADS]: [ Moves.FUSION_BOLT, Moves.BULK_UP, Moves.SHORE_UP, Moves.SUNSTEEL_STRIKE ], - [Species.IRON_BUNDLE]: [ Moves.EARTH_POWER, Moves.BOUNCY_BUBBLE, Moves.NASTY_PLOT, Moves.STEAM_ERUPTION ], + [Species.IRON_BUNDLE]: [ Moves.EARTH_POWER, Moves.SPLISHY_SPLASH, Moves.VOLT_SWITCH, Moves.NASTY_PLOT ], [Species.IRON_HANDS]: [ Moves.DRAIN_PUNCH, Moves.BULK_UP, Moves.PLASMA_FISTS, Moves.ICE_HAMMER ], [Species.IRON_JUGULIS]: [ Moves.FIERY_WRATH, Moves.ROOST, Moves.NASTY_PLOT, Moves.OBLIVION_WING ], [Species.IRON_MOTH]: [ Moves.EARTH_POWER, Moves.SEARING_SHOT, Moves.MALIGNANT_CHAIN, Moves.QUIVER_DANCE ], @@ -566,7 +566,7 @@ export const speciesEggMoves = { [Species.IRON_VALIANT]: [ Moves.PLASMA_FISTS, Moves.NO_RETREAT, Moves.SECRET_SWORD, Moves.MAGICAL_TORQUE ], [Species.KORAIDON]: [ Moves.SUNSTEEL_STRIKE, Moves.SOLAR_BLADE, Moves.DRAGON_DARTS, Moves.BITTER_BLADE ], [Species.MIRAIDON]: [ Moves.ICE_BEAM, Moves.CLANGOROUS_SOUL, Moves.CORE_ENFORCER, Moves.RISING_VOLTAGE ], - [Species.WALKING_WAKE]: [ Moves.BOUNCY_BUBBLE, Moves.NASTY_PLOT, Moves.SLUDGE_WAVE, Moves.CORE_ENFORCER ], + [Species.WALKING_WAKE]: [ Moves.BOUNCY_BUBBLE, Moves.FUSION_FLARE, Moves.SLUDGE_WAVE, Moves.CORE_ENFORCER ], [Species.IRON_LEAVES]: [ Moves.BITTER_BLADE, Moves.U_TURN, Moves.MIGHTY_CLEAVE, Moves.VICTORY_DANCE ], [Species.POLTCHAGEIST]: [ Moves.PARABOLIC_CHARGE, Moves.BOUNCY_BUBBLE, Moves.LEECH_SEED, Moves.SPARKLY_SWIRL ], [Species.OKIDOGI]: [ Moves.COMBAT_TORQUE, Moves.TIDY_UP, Moves.DIRE_CLAW, Moves.WICKED_BLOW ], @@ -578,7 +578,7 @@ export const speciesEggMoves = { [Species.IRON_BOULDER]: [ Moves.PSYBLADE, Moves.KOWTOW_CLEAVE, Moves.STONE_AXE, Moves.BITTER_BLADE ], [Species.IRON_CROWN]: [ Moves.NASTY_PLOT, Moves.SECRET_SWORD, Moves.PSYSTRIKE, Moves.ELECTRO_DRIFT ], [Species.TERAPAGOS]: [ Moves.MOONBLAST, Moves.RECOVER, Moves.ICE_BEAM, Moves.SHELL_SMASH ], - [Species.PECHARUNT]: [ Moves.TAKE_HEART, Moves.BODY_PRESS, Moves.SAPPY_SEED, Moves.KINGS_SHIELD ], + [Species.PECHARUNT]: [ Moves.TAKE_HEART, Moves.BODY_PRESS, Moves.SAPPY_SEED, Moves.ASTRAL_BARRAGE ], [Species.PALDEA_TAUROS]: [ Moves.NO_RETREAT, Moves.BLAZING_TORQUE, Moves.AQUA_STEP, Moves.THUNDEROUS_KICK ], [Species.PALDEA_WOOPER]: [ Moves.STONE_AXE, Moves.RECOVER, Moves.BANEFUL_BUNKER, Moves.BARB_BARRAGE ], [Species.BLOODMOON_URSALUNA]: [ Moves.NASTY_PLOT, Moves.ROCK_POLISH, Moves.SANDSEAR_STORM, Moves.BOOMBURST ] diff --git a/src/data/balance/passives.ts b/src/data/balance/passives.ts index 3b30a629a4b..df347909d49 100644 --- a/src/data/balance/passives.ts +++ b/src/data/balance/passives.ts @@ -12,7 +12,7 @@ interface StarterPassiveAbilities { export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.BULBASAUR]: { 0: Abilities.GRASSY_SURGE }, [Species.CHARMANDER]: { 0: Abilities.BEAST_BOOST }, - [Species.SQUIRTLE]: { 0: Abilities.STURDY }, + [Species.SQUIRTLE]: { 0: Abilities.DAUNTLESS_SHIELD }, [Species.CATERPIE]: { 0: Abilities.MAGICIAN }, [Species.WEEDLE]: { 0: Abilities.TINTED_LENS }, [Species.PIDGEY]: { 0: Abilities.SHEER_FORCE }, @@ -57,7 +57,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.KOFFING]: { 0: Abilities.PARENTAL_BOND }, [Species.RHYHORN]: { 0: Abilities.FILTER }, [Species.TANGELA]: { 0: Abilities.SEED_SOWER }, - [Species.KANGASKHAN]: { 0: Abilities.GUTS }, + [Species.KANGASKHAN]: { 0: Abilities.TECHNICIAN }, [Species.HORSEA]: { 0: Abilities.DRAGONS_MAW }, [Species.GOLDEEN]: { 0: Abilities.MULTISCALE }, [Species.STARYU]: { 0: Abilities.REGENERATOR }, @@ -129,7 +129,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.RAIKOU]: { 0: Abilities.BEAST_BOOST }, [Species.ENTEI]: { 0: Abilities.BEAST_BOOST }, [Species.SUICUNE]: { 0: Abilities.BEAST_BOOST }, - [Species.LARVITAR]: { 0: Abilities.SAND_RUSH }, + [Species.LARVITAR]: { 0: Abilities.SOLID_ROCK }, [Species.LUGIA]: { 0: Abilities.DELTA_STREAM }, [Species.HO_OH]: { 0: Abilities.MAGIC_GUARD }, [Species.CELEBI]: { 0: Abilities.PSYCHIC_SURGE }, @@ -246,11 +246,11 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.MANTYKE]: { 0: Abilities.UNAWARE }, [Species.SNOVER]: { 0: Abilities.GRASSY_SURGE }, [Species.ROTOM]: { 0: Abilities.HADRON_ENGINE }, - [Species.UXIE]: { 0: Abilities.UNAWARE }, + [Species.UXIE]: { 0: Abilities.UNNERVE }, [Species.MESPRIT]: { 0: Abilities.MOODY }, [Species.AZELF]: { 0: Abilities.NEUROFORCE }, - [Species.DIALGA]: { 0: Abilities.LEVITATE }, - [Species.PALKIA]: { 0: Abilities.SPEED_BOOST }, + [Species.DIALGA]: { 0: Abilities.BERSERK }, + [Species.PALKIA]: { 0: Abilities.BERSERK }, [Species.HEATRAN]: { 0: Abilities.EARTH_EATER }, [Species.REGIGIGAS]: { 0: Abilities.SCRAPPY }, [Species.GIRATINA]: { 0: Abilities.SHADOW_SHIELD }, @@ -285,7 +285,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.SEWADDLE]: { 0: Abilities.SHARPNESS }, [Species.VENIPEDE]: { 0: Abilities.STAMINA }, [Species.COTTONEE]: { 0: Abilities.FLUFFY }, - [Species.PETILIL]: { 0: Abilities.SIMPLE }, + [Species.PETILIL]: { 0: Abilities.FLOWER_VEIL }, [Species.BASCULIN]: { 0: Abilities.SUPREME_OVERLORD }, [Species.SANDILE]: { 0: Abilities.TOUGH_CLAWS }, [Species.DARUMAKA]: { 0: Abilities.GORILLA_TACTICS }, @@ -347,7 +347,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.CHESPIN]: { 0: Abilities.DAUNTLESS_SHIELD }, [Species.FENNEKIN]: { 0: Abilities.PSYCHIC_SURGE }, [Species.FROAKIE]: { 0: Abilities.STAKEOUT }, - [Species.BUNNELBY]: { 0: Abilities.GUTS }, + [Species.BUNNELBY]: { 0: Abilities.THICK_FAT }, [Species.FLETCHLING]: { 0: Abilities.MAGIC_GUARD }, [Species.SCATTERBUG]: { 0: Abilities.PRANKSTER }, [Species.LITLEO]: { 0: Abilities.BEAST_BOOST }, @@ -380,7 +380,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.ZYGARDE]: { 0: Abilities.ADAPTABILITY }, [Species.DIANCIE]: { 0: Abilities.PRISM_ARMOR }, [Species.HOOPA]: { 0: Abilities.OPPORTUNIST }, - [Species.VOLCANION]: { 0: Abilities.FILTER }, + [Species.VOLCANION]: { 0: Abilities.NEUTRALIZING_GAS }, [Species.ETERNAL_FLOETTE]: { 0: Abilities.MAGIC_GUARD }, [Species.ROWLET]: { 0: Abilities.SNIPER }, @@ -395,7 +395,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.ROCKRUFF]: { 0: Abilities.ROCKY_PAYLOAD }, [Species.WISHIWASHI]: { 0: Abilities.REGENERATOR }, [Species.MAREANIE]: { 0: Abilities.TOXIC_DEBRIS }, - [Species.MUDBRAY]: { 0: Abilities.CUD_CHEW }, + [Species.MUDBRAY]: { 0: Abilities.SAP_SIPPER }, [Species.DEWPIDER]: { 0: Abilities.TINTED_LENS }, [Species.FOMANTIS]: { 0: Abilities.SHARPNESS }, [Species.MORELULL]: { 0: Abilities.TRIAGE }, @@ -419,8 +419,8 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.DHELMISE]: { 0: Abilities.WATER_BUBBLE }, [Species.JANGMO_O]: { 0: Abilities.DAUNTLESS_SHIELD }, [Species.TAPU_KOKO]: { 0: Abilities.DAUNTLESS_SHIELD }, - [Species.TAPU_LELE]: { 0: Abilities.SHEER_FORCE }, - [Species.TAPU_BULU]: { 0: Abilities.TRIAGE }, + [Species.TAPU_LELE]: { 0: Abilities.BERSERK }, + [Species.TAPU_BULU]: { 0: Abilities.FLOWER_VEIL }, [Species.TAPU_FINI]: { 0: Abilities.FAIRY_AURA }, [Species.COSMOG]: { 0: Abilities.BEAST_BOOST }, [Species.NIHILEGO]: { 0: Abilities.LEVITATE }, @@ -542,7 +542,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.VAROOM]: { 0: Abilities.LEVITATE }, [Species.CYCLIZAR]: { 0: Abilities.PROTEAN }, [Species.ORTHWORM]: { 0: Abilities.REGENERATOR }, - [Species.GLIMMET]: { 0: Abilities.LEVITATE }, + [Species.GLIMMET]: { 0: Abilities.TERA_SHELL }, [Species.GREAVARD]: { 0: Abilities.UNAWARE }, [Species.FLAMIGO]: { 0: Abilities.MOXIE }, [Species.CETODDLE]: { 0: Abilities.REFRIGERATE }, diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 9cc693dc33c..0e101c7155b 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -92,6 +92,7 @@ export class SpeciesFormEvolution { public item: EvolutionItem | null; public condition: SpeciesEvolutionCondition | null; public wildDelay: SpeciesWildEvolutionDelay; + public description: string = ""; constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) { this.speciesId = speciesId; @@ -101,6 +102,23 @@ export class SpeciesFormEvolution { this.item = item || EvolutionItem.NONE; this.condition = condition; this.wildDelay = wildDelay ?? SpeciesWildEvolutionDelay.NONE; + + const strings: string[] = []; + if (this.level > 1) { + strings.push(i18next.t("pokemonEvolutions:level") + ` ${this.level}`); + } + if (this.item) { + const itemDescription = i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.item].toUpperCase()}`); + const rarity = this.item > 50 ? i18next.t("pokemonEvolutions:ULTRA") : i18next.t("pokemonEvolutions:GREAT"); + strings.push(i18next.t("pokemonEvolutions:using") + itemDescription + ` (${rarity})`); + } + if (this.condition) { + strings.push(this.condition.description); + } + this.description = strings + .filter(str => str !== "") + .map((str, index) => index > 0 ? str[0].toLowerCase() + str.slice(1) : str) + .join(i18next.t("pokemonEvolutions:connector")); } } @@ -237,6 +255,7 @@ class WeatherEvolutionCondition extends SpeciesEvolutionCondition { constructor(weatherTypes: WeatherType[]) { super(() => weatherTypes.indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1); this.weatherTypes = weatherTypes; + this.description = i18next.t("pokemonEvolutions:weather"); } } @@ -1377,7 +1396,7 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [Species.TANDEMAUS]: [ new SpeciesFormEvolution(Species.MAUSHOLD, "", "three", 25, null, new TandemausEvolutionCondition()), - new SpeciesEvolution(Species.MAUSHOLD, 25, null, null) + new SpeciesFormEvolution(Species.MAUSHOLD, "", "four", 25, null, null) ], [Species.FIDOUGH]: [ new SpeciesEvolution(Species.DACHSBUN, 26, null, null) @@ -1540,7 +1559,7 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [Species.DUNSPARCE]: [ new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new DunsparceEvolutionCondition(), SpeciesWildEvolutionDelay.LONG), - new SpeciesEvolution(Species.DUDUNSPARCE, 32, null, new MoveEvolutionCondition(Moves.HYPER_DRILL), SpeciesWildEvolutionDelay.LONG) + new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "two-segment", 32, null, new MoveEvolutionCondition(Moves.HYPER_DRILL), SpeciesWildEvolutionDelay.LONG) ], [Species.GLIGAR]: [ new SpeciesEvolution(Species.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new TimeOfDayEvolutionCondition("night") /* Razor fang at night*/, SpeciesWildEvolutionDelay.VERY_LONG) diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index ee33142e981..dcf0766d005 100644 --- a/src/data/balance/starters.ts +++ b/src/data/balance/starters.ts @@ -128,7 +128,7 @@ export const speciesStarterCosts = { [Species.YANMA]: 3, [Species.WOOPER]: 2, [Species.MURKROW]: 3, - [Species.MISDREAVUS]: 2, + [Species.MISDREAVUS]: 3, [Species.UNOWN]: 1, [Species.GIRAFARIG]: 3, [Species.PINECO]: 2, @@ -245,7 +245,7 @@ export const speciesStarterCosts = { [Species.KRICKETOT]: 1, [Species.SHINX]: 2, [Species.BUDEW]: 3, - [Species.CRANIDOS]: 3, + [Species.CRANIDOS]: 2, [Species.SHIELDON]: 3, [Species.BURMY]: 2, [Species.COMBEE]: 2, @@ -274,7 +274,7 @@ export const speciesStarterCosts = { [Species.FINNEON]: 1, [Species.MANTYKE]: 2, [Species.SNOVER]: 2, - [Species.ROTOM]: 5, + [Species.ROTOM]: 4, [Species.UXIE]: 5, [Species.MESPRIT]: 5, [Species.AZELF]: 6, @@ -287,7 +287,7 @@ export const speciesStarterCosts = { [Species.PHIONE]: 4, [Species.MANAPHY]: 7, [Species.DARKRAI]: 7, - [Species.SHAYMIN]: 5, + [Species.SHAYMIN]: 6, [Species.ARCEUS]: 9, [Species.VICTINI]: 7, diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 02beab780c5..0226aef79d1 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -328,7 +328,8 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge this.move = move; this.known = known; const moveKey = Moves[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("") as unknown as string; - this.description = i18next.t("pokemonEvolutions:Forms.moveLearned", { move: i18next.t(`move:${moveKey}.name`) }); + this.description = known ? i18next.t("pokemonEvolutions:Forms.moveLearned", { move: i18next.t(`move:${moveKey}.name`) }) : + i18next.t("pokemonEvolutions:Forms.moveForgotten", { move: i18next.t(`move:${moveKey}.name`) }); } canChange(pokemon: Pokemon): boolean { diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index edc9cbccfd2..55da6a7e76c 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -324,8 +324,8 @@ export abstract class PokemonSpeciesForm { return ret; } - getSpriteAtlasPath(female: boolean, formIndex?: number, shiny?: boolean, variant?: number): string { - const spriteId = this.getSpriteId(female, formIndex, shiny, variant).replace(/\_{2}/g, "/"); + getSpriteAtlasPath(female: boolean, formIndex?: number, shiny?: boolean, variant?: number, back?: boolean): string { + const spriteId = this.getSpriteId(female, formIndex, shiny, variant, back).replace(/\_{2}/g, "/"); return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`; } @@ -346,8 +346,8 @@ export abstract class PokemonSpeciesForm { return `${back ? "back__" : ""}${shiny && (!variantSet || (!variant && !variantSet[variant || 0])) ? "shiny__" : ""}${baseSpriteKey}${shiny && variantSet && variantSet[variant] === 2 ? `_${variant + 1}` : ""}`; } - getSpriteKey(female: boolean, formIndex?: number, shiny?: boolean, variant?: number): string { - return `pkmn__${this.getSpriteId(female, formIndex, shiny, variant)}`; + getSpriteKey(female: boolean, formIndex?: number, shiny?: boolean, variant?: number, back?: boolean): string { + return `pkmn__${this.getSpriteId(female, formIndex, shiny, variant, back)}`; } abstract getFormSpriteKey(formIndex?: number): string; @@ -520,10 +520,10 @@ export abstract class PokemonSpeciesForm { return true; } - loadAssets(female: boolean, formIndex?: number, shiny?: boolean, variant?: Variant, startLoad?: boolean): Promise { + loadAssets(female: boolean, formIndex?: number, shiny?: boolean, variant?: Variant, startLoad?: boolean, back?: boolean): Promise { return new Promise(resolve => { - const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant); - globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant)); + const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back); + globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back)); globalScene.load.audio(`${this.getCryKey(formIndex)}`, `audio/${this.getCryKey(formIndex)}.m4a`); globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => { const originalWarn = console.warn; @@ -533,7 +533,7 @@ export abstract class PokemonSpeciesForm { console.warn = originalWarn; if (!(globalScene.anims.exists(spriteKey))) { globalScene.anims.create({ - key: this.getSpriteKey(female, formIndex, shiny, variant), + key: this.getSpriteKey(female, formIndex, shiny, variant, back), frames: frameNames, frameRate: 10, repeat: -1 @@ -541,7 +541,7 @@ export abstract class PokemonSpeciesForm { } else { globalScene.anims.get(spriteKey).frameRate = 10; } - const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, ""); + const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant, back).replace("variant/", "").replace(/_[1-3]$/, ""); globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve()); }); if (startLoad) { @@ -1027,15 +1027,15 @@ export function initSpecies() { ), new PokemonSpecies(Species.WEEDLE, 1, false, false, false, "Hairy Bug Pokémon", Type.BUG, Type.POISON, 0.3, 3.2, Abilities.SHIELD_DUST, Abilities.NONE, Abilities.RUN_AWAY, 195, 40, 35, 30, 20, 20, 50, 255, 70, 39, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.KAKUNA, 1, false, false, false, "Cocoon Pokémon", Type.BUG, Type.POISON, 0.6, 10, Abilities.SHED_SKIN, Abilities.NONE, Abilities.SHED_SKIN, 205, 45, 25, 50, 25, 25, 35, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(Species.BEEDRILL, 1, false, false, false, "Poison Bee Pokémon", Type.BUG, Type.POISON, 1, 29.5, Abilities.SWARM, Abilities.NONE, Abilities.SNIPER, 395, 65, 90, 40, 45, 80, 75, 45, 70, 178, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", Type.BUG, Type.POISON, 1, 29.5, Abilities.SWARM, Abilities.NONE, Abilities.SNIPER, 395, 65, 90, 40, 45, 80, 75, 45, 70, 178, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.BUG, Type.POISON, 1.4, 40.5, Abilities.ADAPTABILITY, Abilities.NONE, Abilities.ADAPTABILITY, 495, 65, 150, 40, 15, 80, 145, 45, 70, 178), + new PokemonSpecies(Species.BEEDRILL, 1, false, false, false, "Poison Bee Pokémon", Type.BUG, Type.POISON, 1, 29.5, Abilities.SWARM, Abilities.NONE, Abilities.SNIPER, 395, 65, 90, 40, 45, 80, 75, 45, 70, 198, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", Type.BUG, Type.POISON, 1, 29.5, Abilities.SWARM, Abilities.NONE, Abilities.SNIPER, 395, 65, 90, 40, 45, 80, 75, 45, 70, 198, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.BUG, Type.POISON, 1.4, 40.5, Abilities.ADAPTABILITY, Abilities.NONE, Abilities.ADAPTABILITY, 495, 65, 150, 40, 15, 80, 145, 45, 70, 198), ), new PokemonSpecies(Species.PIDGEY, 1, false, false, false, "Tiny Bird Pokémon", Type.NORMAL, Type.FLYING, 0.3, 1.8, Abilities.KEEN_EYE, Abilities.TANGLED_FEET, Abilities.BIG_PECKS, 251, 40, 45, 40, 35, 35, 56, 255, 70, 50, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.PIDGEOTTO, 1, false, false, false, "Bird Pokémon", Type.NORMAL, Type.FLYING, 1.1, 30, Abilities.KEEN_EYE, Abilities.TANGLED_FEET, Abilities.BIG_PECKS, 349, 63, 60, 55, 50, 50, 71, 120, 70, 122, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(Species.PIDGEOT, 1, false, false, false, "Bird Pokémon", Type.NORMAL, Type.FLYING, 1.5, 39.5, Abilities.KEEN_EYE, Abilities.TANGLED_FEET, Abilities.BIG_PECKS, 479, 83, 80, 75, 70, 70, 101, 45, 70, 216, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Normal", "", Type.NORMAL, Type.FLYING, 1.5, 39.5, Abilities.KEEN_EYE, Abilities.TANGLED_FEET, Abilities.BIG_PECKS, 479, 83, 80, 75, 70, 70, 101, 45, 70, 216, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.NORMAL, Type.FLYING, 2.2, 50.5, Abilities.NO_GUARD, Abilities.NO_GUARD, Abilities.NO_GUARD, 579, 83, 80, 80, 135, 80, 121, 45, 70, 216), + new PokemonSpecies(Species.PIDGEOT, 1, false, false, false, "Bird Pokémon", Type.NORMAL, Type.FLYING, 1.5, 39.5, Abilities.KEEN_EYE, Abilities.TANGLED_FEET, Abilities.BIG_PECKS, 479, 83, 80, 75, 70, 70, 101, 45, 70, 240, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Normal", "", Type.NORMAL, Type.FLYING, 1.5, 39.5, Abilities.KEEN_EYE, Abilities.TANGLED_FEET, Abilities.BIG_PECKS, 479, 83, 80, 75, 70, 70, 101, 45, 70, 240, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.NORMAL, Type.FLYING, 2.2, 50.5, Abilities.NO_GUARD, Abilities.NO_GUARD, Abilities.NO_GUARD, 579, 83, 80, 80, 135, 80, 121, 45, 70, 240), ), new PokemonSpecies(Species.RATTATA, 1, false, false, false, "Mouse Pokémon", Type.NORMAL, null, 0.3, 3.5, Abilities.RUN_AWAY, Abilities.GUTS, Abilities.HUSTLE, 253, 30, 56, 35, 25, 35, 72, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, true), new PokemonSpecies(Species.RATICATE, 1, false, false, false, "Mouse Pokémon", Type.NORMAL, null, 0.7, 18.5, Abilities.RUN_AWAY, Abilities.GUTS, Abilities.HUSTLE, 413, 55, 81, 60, 50, 70, 97, 127, 70, 145, GrowthRate.MEDIUM_FAST, 50, true), @@ -1108,12 +1108,12 @@ export function initSpecies() { ), new PokemonSpecies(Species.BELLSPROUT, 1, false, false, false, "Flower Pokémon", Type.GRASS, Type.POISON, 0.7, 4, Abilities.CHLOROPHYLL, Abilities.NONE, Abilities.GLUTTONY, 300, 50, 75, 35, 70, 30, 40, 255, 70, 60, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.WEEPINBELL, 1, false, false, false, "Flycatcher Pokémon", Type.GRASS, Type.POISON, 1, 6.4, Abilities.CHLOROPHYLL, Abilities.NONE, Abilities.GLUTTONY, 390, 65, 90, 50, 85, 45, 55, 120, 70, 137, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(Species.VICTREEBEL, 1, false, false, false, "Flycatcher Pokémon", Type.GRASS, Type.POISON, 1.7, 15.5, Abilities.CHLOROPHYLL, Abilities.NONE, Abilities.GLUTTONY, 490, 80, 105, 65, 100, 70, 70, 45, 70, 221, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(Species.VICTREEBEL, 1, false, false, false, "Flycatcher Pokémon", Type.GRASS, Type.POISON, 1.7, 15.5, Abilities.CHLOROPHYLL, Abilities.NONE, Abilities.GLUTTONY, 490, 80, 105, 65, 100, 70, 70, 45, 70, 245, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.TENTACOOL, 1, false, false, false, "Jellyfish Pokémon", Type.WATER, Type.POISON, 0.9, 45.5, Abilities.CLEAR_BODY, Abilities.LIQUID_OOZE, Abilities.RAIN_DISH, 335, 40, 40, 35, 50, 100, 70, 190, 50, 67, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.TENTACRUEL, 1, false, false, false, "Jellyfish Pokémon", Type.WATER, Type.POISON, 1.6, 55, Abilities.CLEAR_BODY, Abilities.LIQUID_OOZE, Abilities.RAIN_DISH, 515, 80, 70, 65, 80, 120, 100, 60, 50, 180, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.GEODUDE, 1, false, false, false, "Rock Pokémon", Type.ROCK, Type.GROUND, 0.4, 20, Abilities.ROCK_HEAD, Abilities.STURDY, Abilities.SAND_VEIL, 300, 40, 80, 100, 30, 30, 20, 255, 70, 60, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.GRAVELER, 1, false, false, false, "Rock Pokémon", Type.ROCK, Type.GROUND, 1, 105, Abilities.ROCK_HEAD, Abilities.STURDY, Abilities.SAND_VEIL, 390, 55, 95, 115, 45, 45, 35, 120, 70, 137, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(Species.GOLEM, 1, false, false, false, "Megaton Pokémon", Type.ROCK, Type.GROUND, 1.4, 300, Abilities.ROCK_HEAD, Abilities.STURDY, Abilities.SAND_VEIL, 495, 80, 120, 130, 55, 65, 45, 45, 70, 223, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(Species.GOLEM, 1, false, false, false, "Megaton Pokémon", Type.ROCK, Type.GROUND, 1.4, 300, Abilities.ROCK_HEAD, Abilities.STURDY, Abilities.SAND_VEIL, 495, 80, 120, 130, 55, 65, 45, 45, 70, 248, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.PONYTA, 1, false, false, false, "Fire Horse Pokémon", Type.FIRE, null, 1, 30, Abilities.RUN_AWAY, Abilities.FLASH_FIRE, Abilities.FLAME_BODY, 410, 50, 85, 55, 65, 65, 90, 190, 50, 82, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.RAPIDASH, 1, false, false, false, "Fire Horse Pokémon", Type.FIRE, null, 1.7, 95, Abilities.RUN_AWAY, Abilities.FLASH_FIRE, Abilities.FLAME_BODY, 500, 65, 100, 70, 80, 80, 105, 60, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.SLOWPOKE, 1, false, false, false, "Dopey Pokémon", Type.WATER, Type.PSYCHIC, 1.2, 36, Abilities.OBLIVIOUS, Abilities.OWN_TEMPO, Abilities.REGENERATOR, 315, 90, 65, 65, 40, 40, 15, 190, 50, 63, GrowthRate.MEDIUM_FAST, 50, false), @@ -1227,13 +1227,13 @@ export function initSpecies() { new PokemonSpecies(Species.MEW, 1, false, false, true, "New Species Pokémon", Type.PSYCHIC, null, 0.4, 4, Abilities.SYNCHRONIZE, Abilities.NONE, Abilities.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 300, GrowthRate.MEDIUM_SLOW, null, false), new PokemonSpecies(Species.CHIKORITA, 2, false, false, false, "Leaf Pokémon", Type.GRASS, null, 0.9, 6.4, Abilities.OVERGROW, Abilities.NONE, Abilities.LEAF_GUARD, 318, 45, 49, 65, 49, 65, 45, 45, 70, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.BAYLEEF, 2, false, false, false, "Leaf Pokémon", Type.GRASS, null, 1.2, 15.8, Abilities.OVERGROW, Abilities.NONE, Abilities.LEAF_GUARD, 405, 60, 62, 80, 63, 80, 60, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(Species.MEGANIUM, 2, false, false, false, "Herb Pokémon", Type.GRASS, null, 1.8, 100.5, Abilities.OVERGROW, Abilities.NONE, Abilities.LEAF_GUARD, 525, 80, 82, 100, 83, 100, 80, 45, 70, 236, GrowthRate.MEDIUM_SLOW, 87.5, true), + new PokemonSpecies(Species.MEGANIUM, 2, false, false, false, "Herb Pokémon", Type.GRASS, null, 1.8, 100.5, Abilities.OVERGROW, Abilities.NONE, Abilities.LEAF_GUARD, 525, 80, 82, 100, 83, 100, 80, 45, 70, 263, GrowthRate.MEDIUM_SLOW, 87.5, true), new PokemonSpecies(Species.CYNDAQUIL, 2, false, false, false, "Fire Mouse Pokémon", Type.FIRE, null, 0.5, 7.9, Abilities.BLAZE, Abilities.NONE, Abilities.FLASH_FIRE, 309, 39, 52, 43, 60, 50, 65, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.QUILAVA, 2, false, false, false, "Volcano Pokémon", Type.FIRE, null, 0.9, 19, Abilities.BLAZE, Abilities.NONE, Abilities.FLASH_FIRE, 405, 58, 64, 58, 80, 65, 80, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(Species.TYPHLOSION, 2, false, false, false, "Volcano Pokémon", Type.FIRE, null, 1.7, 79.5, Abilities.BLAZE, Abilities.NONE, Abilities.FLASH_FIRE, 534, 78, 84, 78, 109, 85, 100, 45, 70, 240, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(Species.TYPHLOSION, 2, false, false, false, "Volcano Pokémon", Type.FIRE, null, 1.7, 79.5, Abilities.BLAZE, Abilities.NONE, Abilities.FLASH_FIRE, 534, 78, 84, 78, 109, 85, 100, 45, 70, 267, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.TOTODILE, 2, false, false, false, "Big Jaw Pokémon", Type.WATER, null, 0.6, 9.5, Abilities.TORRENT, Abilities.NONE, Abilities.SHEER_FORCE, 314, 50, 65, 64, 44, 48, 43, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.CROCONAW, 2, false, false, false, "Big Jaw Pokémon", Type.WATER, null, 1.1, 25, Abilities.TORRENT, Abilities.NONE, Abilities.SHEER_FORCE, 405, 65, 80, 80, 59, 63, 58, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(Species.FERALIGATR, 2, false, false, false, "Big Jaw Pokémon", Type.WATER, null, 2.3, 88.8, Abilities.TORRENT, Abilities.NONE, Abilities.SHEER_FORCE, 530, 85, 105, 100, 79, 83, 78, 45, 70, 239, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(Species.FERALIGATR, 2, false, false, false, "Big Jaw Pokémon", Type.WATER, null, 2.3, 88.8, Abilities.TORRENT, Abilities.NONE, Abilities.SHEER_FORCE, 530, 85, 105, 100, 79, 83, 78, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.SENTRET, 2, false, false, false, "Scout Pokémon", Type.NORMAL, null, 0.8, 6, Abilities.RUN_AWAY, Abilities.KEEN_EYE, Abilities.FRISK, 215, 35, 46, 34, 35, 45, 20, 255, 70, 43, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.FURRET, 2, false, false, false, "Long Body Pokémon", Type.NORMAL, null, 1.8, 32.5, Abilities.RUN_AWAY, Abilities.KEEN_EYE, Abilities.FRISK, 415, 85, 76, 64, 45, 55, 90, 90, 70, 145, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.HOOTHOOT, 2, false, false, false, "Owl Pokémon", Type.NORMAL, Type.FLYING, 0.7, 21.2, Abilities.INSOMNIA, Abilities.KEEN_EYE, Abilities.TINTED_LENS, 262, 60, 30, 30, 36, 56, 50, 255, 50, 52, GrowthRate.MEDIUM_FAST, 50, false), @@ -1257,9 +1257,9 @@ export function initSpecies() { new PokemonSpecies(Species.XATU, 2, false, false, false, "Mystic Pokémon", Type.PSYCHIC, Type.FLYING, 1.5, 15, Abilities.SYNCHRONIZE, Abilities.EARLY_BIRD, Abilities.MAGIC_BOUNCE, 470, 65, 75, 70, 95, 70, 95, 75, 50, 165, GrowthRate.MEDIUM_FAST, 50, true), new PokemonSpecies(Species.MAREEP, 2, false, false, false, "Wool Pokémon", Type.ELECTRIC, null, 0.6, 7.8, Abilities.STATIC, Abilities.NONE, Abilities.PLUS, 280, 55, 40, 40, 65, 45, 35, 235, 70, 56, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.FLAAFFY, 2, false, false, false, "Wool Pokémon", Type.ELECTRIC, null, 0.8, 13.3, Abilities.STATIC, Abilities.NONE, Abilities.PLUS, 365, 70, 55, 55, 80, 60, 45, 120, 70, 128, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(Species.AMPHAROS, 2, false, false, false, "Light Pokémon", Type.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.PLUS, 510, 90, 75, 85, 115, 90, 55, 45, 70, 230, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Normal", "", Type.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.PLUS, 510, 90, 75, 85, 115, 90, 55, 45, 70, 230, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.ELECTRIC, Type.DRAGON, 1.4, 61.5, Abilities.MOLD_BREAKER, Abilities.NONE, Abilities.MOLD_BREAKER, 610, 90, 95, 105, 165, 110, 45, 45, 70, 230), + new PokemonSpecies(Species.AMPHAROS, 2, false, false, false, "Light Pokémon", Type.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.PLUS, 510, 90, 75, 85, 115, 90, 55, 45, 70, 255, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Normal", "", Type.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.PLUS, 510, 90, 75, 85, 115, 90, 55, 45, 70, 255, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.ELECTRIC, Type.DRAGON, 1.4, 61.5, Abilities.MOLD_BREAKER, Abilities.NONE, Abilities.MOLD_BREAKER, 610, 90, 95, 105, 165, 110, 45, 45, 70, 255), ), new PokemonSpecies(Species.BELLOSSOM, 2, false, false, false, "Flower Pokémon", Type.GRASS, null, 0.4, 5.8, Abilities.CHLOROPHYLL, Abilities.NONE, Abilities.HEALER, 490, 75, 80, 95, 90, 100, 50, 45, 50, 245, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.MARILL, 2, false, false, false, "Aqua Mouse Pokémon", Type.WATER, Type.FAIRY, 0.4, 8.5, Abilities.THICK_FAT, Abilities.HUGE_POWER, Abilities.SAP_SIPPER, 250, 70, 20, 50, 20, 50, 40, 190, 50, 88, GrowthRate.FAST, 50, false), @@ -1268,7 +1268,7 @@ export function initSpecies() { new PokemonSpecies(Species.POLITOED, 2, false, false, false, "Frog Pokémon", Type.WATER, null, 1.1, 33.9, Abilities.WATER_ABSORB, Abilities.DAMP, Abilities.DRIZZLE, 500, 90, 75, 75, 90, 100, 70, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, true), new PokemonSpecies(Species.HOPPIP, 2, false, false, false, "Cottonweed Pokémon", Type.GRASS, Type.FLYING, 0.4, 0.5, Abilities.CHLOROPHYLL, Abilities.LEAF_GUARD, Abilities.INFILTRATOR, 250, 35, 35, 40, 35, 55, 50, 255, 70, 50, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.SKIPLOOM, 2, false, false, false, "Cottonweed Pokémon", Type.GRASS, Type.FLYING, 0.6, 1, Abilities.CHLOROPHYLL, Abilities.LEAF_GUARD, Abilities.INFILTRATOR, 340, 55, 45, 50, 45, 65, 80, 120, 70, 119, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(Species.JUMPLUFF, 2, false, false, false, "Cottonweed Pokémon", Type.GRASS, Type.FLYING, 0.8, 3, Abilities.CHLOROPHYLL, Abilities.LEAF_GUARD, Abilities.INFILTRATOR, 460, 75, 55, 70, 55, 95, 110, 45, 70, 207, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(Species.JUMPLUFF, 2, false, false, false, "Cottonweed Pokémon", Type.GRASS, Type.FLYING, 0.8, 3, Abilities.CHLOROPHYLL, Abilities.LEAF_GUARD, Abilities.INFILTRATOR, 460, 75, 55, 70, 55, 95, 110, 45, 70, 230, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.AIPOM, 2, false, false, false, "Long Tail Pokémon", Type.NORMAL, null, 0.8, 11.5, Abilities.RUN_AWAY, Abilities.PICKUP, Abilities.SKILL_LINK, 360, 55, 70, 55, 40, 55, 85, 45, 70, 72, GrowthRate.FAST, 50, true), new PokemonSpecies(Species.SUNKERN, 2, false, false, false, "Seed Pokémon", Type.GRASS, null, 0.3, 1.8, Abilities.CHLOROPHYLL, Abilities.SOLAR_POWER, Abilities.EARLY_BIRD, 180, 30, 30, 30, 30, 30, 30, 235, 70, 36, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.SUNFLORA, 2, false, false, false, "Sun Pokémon", Type.GRASS, null, 0.8, 8.5, Abilities.CHLOROPHYLL, Abilities.SOLAR_POWER, Abilities.EARLY_BIRD, 425, 75, 75, 55, 105, 85, 30, 120, 70, 149, GrowthRate.MEDIUM_SLOW, 50, false), @@ -1362,7 +1362,7 @@ export function initSpecies() { new PokemonSpecies(Species.ELEKID, 2, false, false, false, "Electric Pokémon", Type.ELECTRIC, null, 0.6, 23.5, Abilities.STATIC, Abilities.NONE, Abilities.VITAL_SPIRIT, 360, 45, 63, 37, 65, 55, 95, 45, 50, 72, GrowthRate.MEDIUM_FAST, 75, false), new PokemonSpecies(Species.MAGBY, 2, false, false, false, "Live Coal Pokémon", Type.FIRE, null, 0.7, 21.4, Abilities.FLAME_BODY, Abilities.NONE, Abilities.VITAL_SPIRIT, 365, 45, 75, 37, 70, 55, 83, 45, 50, 73, GrowthRate.MEDIUM_FAST, 75, false), new PokemonSpecies(Species.MILTANK, 2, false, false, false, "Milk Cow Pokémon", Type.NORMAL, null, 1.2, 75.5, Abilities.THICK_FAT, Abilities.SCRAPPY, Abilities.SAP_SIPPER, 490, 95, 80, 105, 40, 70, 100, 45, 50, 172, GrowthRate.SLOW, 0, false), - new PokemonSpecies(Species.BLISSEY, 2, false, false, false, "Happiness Pokémon", Type.NORMAL, null, 1.5, 46.8, Abilities.NATURAL_CURE, Abilities.SERENE_GRACE, Abilities.HEALER, 540, 255, 10, 10, 75, 135, 55, 30, 140, 635, GrowthRate.FAST, 0, false), + new PokemonSpecies(Species.BLISSEY, 2, false, false, false, "Happiness Pokémon", Type.NORMAL, null, 1.5, 46.8, Abilities.NATURAL_CURE, Abilities.SERENE_GRACE, Abilities.HEALER, 540, 255, 10, 10, 75, 135, 55, 30, 140, 608, GrowthRate.FAST, 0, false), new PokemonSpecies(Species.RAIKOU, 2, true, false, false, "Thunder Pokémon", Type.ELECTRIC, null, 1.9, 178, Abilities.PRESSURE, Abilities.NONE, Abilities.INNER_FOCUS, 580, 90, 85, 75, 115, 100, 115, 3, 35, 290, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.ENTEI, 2, true, false, false, "Volcano Pokémon", Type.FIRE, null, 2.1, 198, Abilities.PRESSURE, Abilities.NONE, Abilities.INNER_FOCUS, 580, 115, 115, 85, 90, 75, 100, 3, 35, 290, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.SUICUNE, 2, true, false, false, "Aurora Pokémon", Type.WATER, null, 2, 187, Abilities.PRESSURE, Abilities.NONE, Abilities.INNER_FOCUS, 580, 100, 75, 115, 90, 115, 85, 3, 35, 290, GrowthRate.SLOW, null, false), @@ -1399,9 +1399,9 @@ export function initSpecies() { new PokemonSpecies(Species.LINOONE, 3, false, false, false, "Rushing Pokémon", Type.NORMAL, null, 0.5, 32.5, Abilities.PICKUP, Abilities.GLUTTONY, Abilities.QUICK_FEET, 420, 78, 70, 61, 50, 61, 100, 90, 50, 147, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.WURMPLE, 3, false, false, false, "Worm Pokémon", Type.BUG, null, 0.3, 3.6, Abilities.SHIELD_DUST, Abilities.NONE, Abilities.RUN_AWAY, 195, 45, 45, 35, 20, 30, 20, 255, 70, 56, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.SILCOON, 3, false, false, false, "Cocoon Pokémon", Type.BUG, null, 0.6, 10, Abilities.SHED_SKIN, Abilities.NONE, Abilities.SHED_SKIN, 205, 50, 35, 55, 25, 25, 15, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(Species.BEAUTIFLY, 3, false, false, false, "Butterfly Pokémon", Type.BUG, Type.FLYING, 1, 28.4, Abilities.SWARM, Abilities.NONE, Abilities.RIVALRY, 395, 60, 70, 50, 100, 50, 65, 45, 70, 178, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(Species.BEAUTIFLY, 3, false, false, false, "Butterfly Pokémon", Type.BUG, Type.FLYING, 1, 28.4, Abilities.SWARM, Abilities.NONE, Abilities.RIVALRY, 395, 60, 70, 50, 100, 50, 65, 45, 70, 198, GrowthRate.MEDIUM_FAST, 50, true), new PokemonSpecies(Species.CASCOON, 3, false, false, false, "Cocoon Pokémon", Type.BUG, null, 0.7, 11.5, Abilities.SHED_SKIN, Abilities.NONE, Abilities.SHED_SKIN, 205, 50, 35, 55, 25, 25, 15, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(Species.DUSTOX, 3, false, false, false, "Poison Moth Pokémon", Type.BUG, Type.POISON, 1.2, 31.6, Abilities.SHIELD_DUST, Abilities.NONE, Abilities.COMPOUND_EYES, 385, 60, 50, 70, 50, 90, 65, 45, 70, 173, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(Species.DUSTOX, 3, false, false, false, "Poison Moth Pokémon", Type.BUG, Type.POISON, 1.2, 31.6, Abilities.SHIELD_DUST, Abilities.NONE, Abilities.COMPOUND_EYES, 385, 60, 50, 70, 50, 90, 65, 45, 70, 193, GrowthRate.MEDIUM_FAST, 50, true), new PokemonSpecies(Species.LOTAD, 3, false, false, false, "Water Weed Pokémon", Type.WATER, Type.GRASS, 0.5, 2.6, Abilities.SWIFT_SWIM, Abilities.RAIN_DISH, Abilities.OWN_TEMPO, 220, 40, 30, 30, 40, 50, 30, 255, 50, 44, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.LOMBRE, 3, false, false, false, "Jolly Pokémon", Type.WATER, Type.GRASS, 1.2, 32.5, Abilities.SWIFT_SWIM, Abilities.RAIN_DISH, Abilities.OWN_TEMPO, 340, 60, 50, 50, 60, 70, 50, 120, 50, 119, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.LUDICOLO, 3, false, false, false, "Carefree Pokémon", Type.WATER, Type.GRASS, 1.5, 55, Abilities.SWIFT_SWIM, Abilities.RAIN_DISH, Abilities.OWN_TEMPO, 480, 80, 70, 70, 90, 100, 70, 45, 50, 240, GrowthRate.MEDIUM_SLOW, 50, true), @@ -1424,7 +1424,7 @@ export function initSpecies() { new PokemonSpecies(Species.BRELOOM, 3, false, false, false, "Mushroom Pokémon", Type.GRASS, Type.FIGHTING, 1.2, 39.2, Abilities.EFFECT_SPORE, Abilities.POISON_HEAL, Abilities.TECHNICIAN, 460, 60, 130, 80, 60, 60, 70, 90, 70, 161, GrowthRate.FLUCTUATING, 50, false), new PokemonSpecies(Species.SLAKOTH, 3, false, false, false, "Slacker Pokémon", Type.NORMAL, null, 0.8, 24, Abilities.TRUANT, Abilities.NONE, Abilities.STALL, 280, 60, 60, 60, 35, 35, 30, 255, 70, 56, GrowthRate.SLOW, 50, false), //Custom Hidden new PokemonSpecies(Species.VIGOROTH, 3, false, false, false, "Wild Monkey Pokémon", Type.NORMAL, null, 1.4, 46.5, Abilities.VITAL_SPIRIT, Abilities.NONE, Abilities.INSOMNIA, 440, 80, 80, 80, 55, 55, 90, 120, 70, 154, GrowthRate.SLOW, 50, false), //Custom Hidden - new PokemonSpecies(Species.SLAKING, 3, false, false, false, "Lazy Pokémon", Type.NORMAL, null, 2, 130.5, Abilities.TRUANT, Abilities.NONE, Abilities.STALL, 670, 150, 160, 100, 95, 65, 100, 45, 70, 252, GrowthRate.SLOW, 50, false), //Custom Hidden + new PokemonSpecies(Species.SLAKING, 3, false, false, false, "Lazy Pokémon", Type.NORMAL, null, 2, 130.5, Abilities.TRUANT, Abilities.NONE, Abilities.STALL, 670, 150, 160, 100, 95, 65, 100, 45, 70, 285, GrowthRate.SLOW, 50, false), //Custom Hidden new PokemonSpecies(Species.NINCADA, 3, false, false, false, "Trainee Pokémon", Type.BUG, Type.GROUND, 0.5, 5.5, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.RUN_AWAY, 266, 31, 45, 90, 30, 30, 40, 255, 50, 53, GrowthRate.ERRATIC, 50, false), new PokemonSpecies(Species.NINJASK, 3, false, false, false, "Ninja Pokémon", Type.BUG, Type.FLYING, 0.8, 12, Abilities.SPEED_BOOST, Abilities.NONE, Abilities.INFILTRATOR, 456, 61, 90, 45, 50, 50, 160, 120, 50, 160, GrowthRate.ERRATIC, 50, false), new PokemonSpecies(Species.SHEDINJA, 3, false, false, false, "Shed Pokémon", Type.BUG, Type.GHOST, 0.8, 1.2, Abilities.WONDER_GUARD, Abilities.NONE, Abilities.NONE, 236, 1, 90, 45, 30, 30, 40, 45, 50, 83, GrowthRate.ERRATIC, null, false), @@ -1580,24 +1580,24 @@ export function initSpecies() { new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.DRAGON, Type.FLYING, 10.8, 392, Abilities.DELTA_STREAM, Abilities.NONE, Abilities.NONE, 780, 105, 180, 100, 180, 100, 115, 45, 0, 340), ), new PokemonSpecies(Species.JIRACHI, 3, false, false, true, "Wish Pokémon", Type.STEEL, Type.PSYCHIC, 0.3, 1.1, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 100, 300, GrowthRate.SLOW, null, false), - new PokemonSpecies(Species.DEOXYS, 3, false, false, true, "DNA Pokémon", Type.PSYCHIC, null, 1.7, 60.8, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 270, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal Forme", "normal", Type.PSYCHIC, null, 1.7, 60.8, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 270, false, "", true), - new PokemonForm("Attack Forme", "attack", Type.PSYCHIC, null, 1.7, 60.8, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 600, 50, 180, 20, 180, 20, 150, 3, 0, 270), - new PokemonForm("Defense Forme", "defense", Type.PSYCHIC, null, 1.7, 60.8, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 600, 50, 70, 160, 70, 160, 90, 3, 0, 270), - new PokemonForm("Speed Forme", "speed", Type.PSYCHIC, null, 1.7, 60.8, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 600, 50, 95, 90, 95, 90, 180, 3, 0, 270), + new PokemonSpecies(Species.DEOXYS, 3, false, false, true, "DNA Pokémon", Type.PSYCHIC, null, 1.7, 60.8, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 300, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal Forme", "normal", Type.PSYCHIC, null, 1.7, 60.8, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 300, false, "", true), + new PokemonForm("Attack Forme", "attack", Type.PSYCHIC, null, 1.7, 60.8, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 600, 50, 180, 20, 180, 20, 150, 3, 0, 300), + new PokemonForm("Defense Forme", "defense", Type.PSYCHIC, null, 1.7, 60.8, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 600, 50, 70, 160, 70, 160, 90, 3, 0, 300), + new PokemonForm("Speed Forme", "speed", Type.PSYCHIC, null, 1.7, 60.8, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 600, 50, 95, 90, 95, 90, 180, 3, 0, 300), ), new PokemonSpecies(Species.TURTWIG, 4, false, false, false, "Tiny Leaf Pokémon", Type.GRASS, null, 0.4, 10.2, Abilities.OVERGROW, Abilities.NONE, Abilities.SHELL_ARMOR, 318, 55, 68, 64, 45, 55, 31, 45, 70, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.GROTLE, 4, false, false, false, "Grove Pokémon", Type.GRASS, null, 1.1, 97, Abilities.OVERGROW, Abilities.NONE, Abilities.SHELL_ARMOR, 405, 75, 89, 85, 55, 65, 36, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(Species.TORTERRA, 4, false, false, false, "Continent Pokémon", Type.GRASS, Type.GROUND, 2.2, 310, Abilities.OVERGROW, Abilities.NONE, Abilities.SHELL_ARMOR, 525, 95, 109, 105, 75, 85, 56, 45, 70, 236, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(Species.TORTERRA, 4, false, false, false, "Continent Pokémon", Type.GRASS, Type.GROUND, 2.2, 310, Abilities.OVERGROW, Abilities.NONE, Abilities.SHELL_ARMOR, 525, 95, 109, 105, 75, 85, 56, 45, 70, 263, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.CHIMCHAR, 4, false, false, false, "Chimp Pokémon", Type.FIRE, null, 0.5, 6.2, Abilities.BLAZE, Abilities.NONE, Abilities.IRON_FIST, 309, 44, 58, 44, 58, 44, 61, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.MONFERNO, 4, false, false, false, "Playful Pokémon", Type.FIRE, Type.FIGHTING, 0.9, 22, Abilities.BLAZE, Abilities.NONE, Abilities.IRON_FIST, 405, 64, 78, 52, 78, 52, 81, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(Species.INFERNAPE, 4, false, false, false, "Flame Pokémon", Type.FIRE, Type.FIGHTING, 1.2, 55, Abilities.BLAZE, Abilities.NONE, Abilities.IRON_FIST, 534, 76, 104, 71, 104, 71, 108, 45, 70, 240, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(Species.INFERNAPE, 4, false, false, false, "Flame Pokémon", Type.FIRE, Type.FIGHTING, 1.2, 55, Abilities.BLAZE, Abilities.NONE, Abilities.IRON_FIST, 534, 76, 104, 71, 104, 71, 108, 45, 70, 267, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.PIPLUP, 4, false, false, false, "Penguin Pokémon", Type.WATER, null, 0.4, 5.2, Abilities.TORRENT, Abilities.NONE, Abilities.COMPETITIVE, 314, 53, 51, 53, 61, 56, 40, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.PRINPLUP, 4, false, false, false, "Penguin Pokémon", Type.WATER, null, 0.8, 23, Abilities.TORRENT, Abilities.NONE, Abilities.COMPETITIVE, 405, 64, 66, 68, 81, 76, 50, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(Species.EMPOLEON, 4, false, false, false, "Emperor Pokémon", Type.WATER, Type.STEEL, 1.7, 84.5, Abilities.TORRENT, Abilities.NONE, Abilities.COMPETITIVE, 530, 84, 86, 88, 111, 101, 60, 45, 70, 239, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(Species.EMPOLEON, 4, false, false, false, "Emperor Pokémon", Type.WATER, Type.STEEL, 1.7, 84.5, Abilities.TORRENT, Abilities.NONE, Abilities.COMPETITIVE, 530, 84, 86, 88, 111, 101, 60, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.STARLY, 4, false, false, false, "Starling Pokémon", Type.NORMAL, Type.FLYING, 0.3, 2, Abilities.KEEN_EYE, Abilities.NONE, Abilities.RECKLESS, 245, 40, 55, 30, 30, 30, 60, 255, 70, 49, GrowthRate.MEDIUM_SLOW, 50, true), new PokemonSpecies(Species.STARAVIA, 4, false, false, false, "Starling Pokémon", Type.NORMAL, Type.FLYING, 0.6, 15.5, Abilities.INTIMIDATE, Abilities.NONE, Abilities.RECKLESS, 340, 55, 75, 50, 40, 40, 80, 120, 70, 119, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(Species.STARAPTOR, 4, false, false, false, "Predator Pokémon", Type.NORMAL, Type.FLYING, 1.2, 24.9, Abilities.INTIMIDATE, Abilities.NONE, Abilities.RECKLESS, 485, 85, 120, 70, 50, 60, 100, 45, 70, 218, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(Species.STARAPTOR, 4, false, false, false, "Predator Pokémon", Type.NORMAL, Type.FLYING, 1.2, 24.9, Abilities.INTIMIDATE, Abilities.NONE, Abilities.RECKLESS, 485, 85, 120, 70, 50, 60, 100, 45, 70, 243, GrowthRate.MEDIUM_SLOW, 50, true), new PokemonSpecies(Species.BIDOOF, 4, false, false, false, "Plump Mouse Pokémon", Type.NORMAL, null, 0.5, 20, Abilities.SIMPLE, Abilities.UNAWARE, Abilities.MOODY, 250, 59, 45, 40, 35, 40, 31, 255, 70, 50, GrowthRate.MEDIUM_FAST, 50, true), new PokemonSpecies(Species.BIBAREL, 4, false, false, false, "Beaver Pokémon", Type.NORMAL, Type.WATER, 1, 31.5, Abilities.SIMPLE, Abilities.UNAWARE, Abilities.MOODY, 410, 79, 85, 60, 55, 60, 71, 127, 70, 144, GrowthRate.MEDIUM_FAST, 50, true), new PokemonSpecies(Species.KRICKETOT, 4, false, false, false, "Cricket Pokémon", Type.BUG, null, 0.3, 2.2, Abilities.SHED_SKIN, Abilities.NONE, Abilities.RUN_AWAY, 194, 37, 25, 41, 25, 41, 25, 255, 70, 39, GrowthRate.MEDIUM_SLOW, 50, true), @@ -1712,11 +1712,11 @@ export function initSpecies() { new PokemonSpecies(Species.FROSLASS, 4, false, false, false, "Snow Land Pokémon", Type.ICE, Type.GHOST, 1.3, 26.6, Abilities.SNOW_CLOAK, Abilities.NONE, Abilities.CURSED_BODY, 480, 70, 80, 70, 80, 70, 110, 75, 50, 168, GrowthRate.MEDIUM_FAST, 0, false), new PokemonSpecies(Species.ROTOM, 4, false, false, false, "Plasma Pokémon", Type.ELECTRIC, Type.GHOST, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 440, 50, 50, 77, 95, 77, 91, 45, 50, 154, GrowthRate.MEDIUM_FAST, null, false, false, new PokemonForm("Normal", "", Type.ELECTRIC, Type.GHOST, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 440, 50, 50, 77, 95, 77, 91, 45, 50, 154, false, null, true), - new PokemonForm("Heat", "heat", Type.ELECTRIC, Type.FIRE, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 154, false, null, true), - new PokemonForm("Wash", "wash", Type.ELECTRIC, Type.WATER, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 154, false, null, true), - new PokemonForm("Frost", "frost", Type.ELECTRIC, Type.ICE, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 154, false, null, true), - new PokemonForm("Fan", "fan", Type.ELECTRIC, Type.FLYING, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 154, false, null, true), - new PokemonForm("Mow", "mow", Type.ELECTRIC, Type.GRASS, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 154, false, null, true), + new PokemonForm("Heat", "heat", Type.ELECTRIC, Type.FIRE, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), + new PokemonForm("Wash", "wash", Type.ELECTRIC, Type.WATER, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), + new PokemonForm("Frost", "frost", Type.ELECTRIC, Type.ICE, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), + new PokemonForm("Fan", "fan", Type.ELECTRIC, Type.FLYING, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), + new PokemonForm("Mow", "mow", Type.ELECTRIC, Type.GRASS, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), ), new PokemonSpecies(Species.UXIE, 4, true, false, false, "Knowledge Pokémon", Type.PSYCHIC, null, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 580, 75, 75, 130, 75, 130, 95, 3, 140, 290, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.MESPRIT, 4, true, false, false, "Emotion Pokémon", Type.PSYCHIC, null, 0.3, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 580, 80, 105, 105, 105, 105, 80, 3, 140, 290, GrowthRate.SLOW, null, false), @@ -1736,44 +1736,44 @@ export function initSpecies() { new PokemonForm("Origin Forme", "origin", Type.GHOST, Type.DRAGON, 6.9, 650, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 680, 150, 120, 100, 120, 100, 90, 3, 0, 340), ), new PokemonSpecies(Species.CRESSELIA, 4, true, false, false, "Lunar Pokémon", Type.PSYCHIC, null, 1.5, 85.6, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 580, 120, 70, 110, 75, 120, 85, 3, 100, 300, GrowthRate.SLOW, 0, false), - new PokemonSpecies(Species.PHIONE, 4, false, false, true, "Sea Drifter Pokémon", Type.WATER, null, 0.4, 3.1, Abilities.HYDRATION, Abilities.NONE, Abilities.NONE, 480, 80, 80, 80, 80, 80, 80, 30, 70, 216, GrowthRate.SLOW, null, false), - new PokemonSpecies(Species.MANAPHY, 4, false, false, true, "Seafaring Pokémon", Type.WATER, null, 0.3, 1.4, Abilities.HYDRATION, Abilities.NONE, Abilities.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 70, 270, GrowthRate.SLOW, null, false), - new PokemonSpecies(Species.DARKRAI, 4, false, false, true, "Pitch-Black Pokémon", Type.DARK, null, 1.5, 50.5, Abilities.BAD_DREAMS, Abilities.NONE, Abilities.NONE, 600, 70, 90, 90, 135, 90, 125, 3, 0, 270, GrowthRate.SLOW, null, false), - new PokemonSpecies(Species.SHAYMIN, 4, false, false, true, "Gratitude Pokémon", Type.GRASS, null, 0.2, 2.1, Abilities.NATURAL_CURE, Abilities.NONE, Abilities.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 270, GrowthRate.MEDIUM_SLOW, null, false, true, - new PokemonForm("Land Forme", "land", Type.GRASS, null, 0.2, 2.1, Abilities.NATURAL_CURE, Abilities.NONE, Abilities.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 270, false, null, true), - new PokemonForm("Sky Forme", "sky", Type.GRASS, Type.FLYING, 0.4, 5.2, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 103, 75, 120, 75, 127, 45, 100, 270), + new PokemonSpecies(Species.PHIONE, 4, false, false, true, "Sea Drifter Pokémon", Type.WATER, null, 0.4, 3.1, Abilities.HYDRATION, Abilities.NONE, Abilities.NONE, 480, 80, 80, 80, 80, 80, 80, 30, 70, 240, GrowthRate.SLOW, null, false), + new PokemonSpecies(Species.MANAPHY, 4, false, false, true, "Seafaring Pokémon", Type.WATER, null, 0.3, 1.4, Abilities.HYDRATION, Abilities.NONE, Abilities.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 70, 300, GrowthRate.SLOW, null, false), + new PokemonSpecies(Species.DARKRAI, 4, false, false, true, "Pitch-Black Pokémon", Type.DARK, null, 1.5, 50.5, Abilities.BAD_DREAMS, Abilities.NONE, Abilities.NONE, 600, 70, 90, 90, 135, 90, 125, 3, 0, 300, GrowthRate.SLOW, null, false), + new PokemonSpecies(Species.SHAYMIN, 4, false, false, true, "Gratitude Pokémon", Type.GRASS, null, 0.2, 2.1, Abilities.NATURAL_CURE, Abilities.NONE, Abilities.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 300, GrowthRate.MEDIUM_SLOW, null, false, true, + new PokemonForm("Land Forme", "land", Type.GRASS, null, 0.2, 2.1, Abilities.NATURAL_CURE, Abilities.NONE, Abilities.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 300, false, null, true), + new PokemonForm("Sky Forme", "sky", Type.GRASS, Type.FLYING, 0.4, 5.2, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 103, 75, 120, 75, 127, 45, 100, 300), ), - new PokemonSpecies(Species.ARCEUS, 4, false, false, true, "Alpha Pokémon", Type.NORMAL, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "normal", Type.NORMAL, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324, false, null, true), - new PokemonForm("Fighting", "fighting", Type.FIGHTING, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Flying", "flying", Type.FLYING, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Poison", "poison", Type.POISON, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Ground", "ground", Type.GROUND, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Rock", "rock", Type.ROCK, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Bug", "bug", Type.BUG, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Ghost", "ghost", Type.GHOST, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Steel", "steel", Type.STEEL, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Fire", "fire", Type.FIRE, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Water", "water", Type.WATER, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Grass", "grass", Type.GRASS, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Electric", "electric", Type.ELECTRIC, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Psychic", "psychic", Type.PSYCHIC, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Ice", "ice", Type.ICE, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Dragon", "dragon", Type.DRAGON, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Dark", "dark", Type.DARK, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("Fairy", "fairy", Type.FAIRY, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), - new PokemonForm("???", "unknown", Type.UNKNOWN, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 324), + new PokemonSpecies(Species.ARCEUS, 4, false, false, true, "Alpha Pokémon", Type.NORMAL, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "normal", Type.NORMAL, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360, false, null, true), + new PokemonForm("Fighting", "fighting", Type.FIGHTING, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Flying", "flying", Type.FLYING, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Poison", "poison", Type.POISON, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Ground", "ground", Type.GROUND, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Rock", "rock", Type.ROCK, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Bug", "bug", Type.BUG, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Ghost", "ghost", Type.GHOST, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Steel", "steel", Type.STEEL, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Fire", "fire", Type.FIRE, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Water", "water", Type.WATER, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Grass", "grass", Type.GRASS, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Electric", "electric", Type.ELECTRIC, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Psychic", "psychic", Type.PSYCHIC, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Ice", "ice", Type.ICE, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Dragon", "dragon", Type.DRAGON, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Dark", "dark", Type.DARK, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Fairy", "fairy", Type.FAIRY, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("???", "unknown", Type.UNKNOWN, null, 3.2, 320, Abilities.MULTITYPE, Abilities.NONE, Abilities.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), ), new PokemonSpecies(Species.VICTINI, 5, false, false, true, "Victory Pokémon", Type.PSYCHIC, Type.FIRE, 0.4, 4, Abilities.VICTORY_STAR, Abilities.NONE, Abilities.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 100, 300, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.SNIVY, 5, false, false, false, "Grass Snake Pokémon", Type.GRASS, null, 0.6, 8.1, Abilities.OVERGROW, Abilities.NONE, Abilities.CONTRARY, 308, 45, 45, 55, 45, 55, 63, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.SERVINE, 5, false, false, false, "Grass Snake Pokémon", Type.GRASS, null, 0.8, 16, Abilities.OVERGROW, Abilities.NONE, Abilities.CONTRARY, 413, 60, 60, 75, 60, 75, 83, 45, 70, 145, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(Species.SERPERIOR, 5, false, false, false, "Regal Pokémon", Type.GRASS, null, 3.3, 63, Abilities.OVERGROW, Abilities.NONE, Abilities.CONTRARY, 528, 75, 75, 95, 75, 95, 113, 45, 70, 238, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(Species.SERPERIOR, 5, false, false, false, "Regal Pokémon", Type.GRASS, null, 3.3, 63, Abilities.OVERGROW, Abilities.NONE, Abilities.CONTRARY, 528, 75, 75, 95, 75, 95, 113, 45, 70, 264, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.TEPIG, 5, false, false, false, "Fire Pig Pokémon", Type.FIRE, null, 0.5, 9.9, Abilities.BLAZE, Abilities.NONE, Abilities.THICK_FAT, 308, 65, 63, 45, 45, 45, 45, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.PIGNITE, 5, false, false, false, "Fire Pig Pokémon", Type.FIRE, Type.FIGHTING, 1, 55.5, Abilities.BLAZE, Abilities.NONE, Abilities.THICK_FAT, 418, 90, 93, 55, 70, 55, 55, 45, 70, 146, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(Species.EMBOAR, 5, false, false, false, "Mega Fire Pig Pokémon", Type.FIRE, Type.FIGHTING, 1.6, 150, Abilities.BLAZE, Abilities.NONE, Abilities.RECKLESS, 528, 110, 123, 65, 100, 65, 65, 45, 70, 238, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(Species.EMBOAR, 5, false, false, false, "Mega Fire Pig Pokémon", Type.FIRE, Type.FIGHTING, 1.6, 150, Abilities.BLAZE, Abilities.NONE, Abilities.RECKLESS, 528, 110, 123, 65, 100, 65, 65, 45, 70, 264, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.OSHAWOTT, 5, false, false, false, "Sea Otter Pokémon", Type.WATER, null, 0.5, 5.9, Abilities.TORRENT, Abilities.NONE, Abilities.SHELL_ARMOR, 308, 55, 55, 45, 63, 45, 45, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.DEWOTT, 5, false, false, false, "Discipline Pokémon", Type.WATER, null, 0.8, 24.5, Abilities.TORRENT, Abilities.NONE, Abilities.SHELL_ARMOR, 413, 75, 75, 60, 83, 60, 60, 45, 70, 145, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(Species.SAMUROTT, 5, false, false, false, "Formidable Pokémon", Type.WATER, null, 1.5, 94.6, Abilities.TORRENT, Abilities.NONE, Abilities.SHELL_ARMOR, 528, 95, 100, 85, 108, 70, 70, 45, 70, 238, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(Species.SAMUROTT, 5, false, false, false, "Formidable Pokémon", Type.WATER, null, 1.5, 94.6, Abilities.TORRENT, Abilities.NONE, Abilities.SHELL_ARMOR, 528, 95, 100, 85, 108, 70, 70, 45, 70, 264, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.PATRAT, 5, false, false, false, "Scout Pokémon", Type.NORMAL, null, 0.5, 11.6, Abilities.RUN_AWAY, Abilities.KEEN_EYE, Abilities.ANALYTIC, 255, 45, 55, 39, 35, 39, 42, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.WATCHOG, 5, false, false, false, "Lookout Pokémon", Type.NORMAL, null, 1.1, 27, Abilities.ILLUMINATE, Abilities.KEEN_EYE, Abilities.ANALYTIC, 420, 60, 85, 69, 60, 69, 77, 255, 70, 147, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.LILLIPUP, 5, false, false, false, "Puppy Pokémon", Type.NORMAL, null, 0.4, 4.1, Abilities.VITAL_SPIRIT, Abilities.PICKUP, Abilities.RUN_AWAY, 275, 45, 60, 45, 25, 45, 55, 255, 50, 55, GrowthRate.MEDIUM_SLOW, 50, false), @@ -1815,7 +1815,7 @@ export function initSpecies() { new PokemonSpecies(Species.SAWK, 5, false, false, false, "Karate Pokémon", Type.FIGHTING, null, 1.4, 51, Abilities.STURDY, Abilities.INNER_FOCUS, Abilities.MOLD_BREAKER, 465, 75, 125, 75, 30, 75, 85, 45, 50, 163, GrowthRate.MEDIUM_FAST, 100, false), new PokemonSpecies(Species.SEWADDLE, 5, false, false, false, "Sewing Pokémon", Type.BUG, Type.GRASS, 0.3, 2.5, Abilities.SWARM, Abilities.CHLOROPHYLL, Abilities.OVERCOAT, 310, 45, 53, 70, 40, 60, 42, 255, 70, 62, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.SWADLOON, 5, false, false, false, "Leaf-Wrapped Pokémon", Type.BUG, Type.GRASS, 0.5, 7.3, Abilities.LEAF_GUARD, Abilities.CHLOROPHYLL, Abilities.OVERCOAT, 380, 55, 63, 90, 50, 80, 42, 120, 70, 133, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(Species.LEAVANNY, 5, false, false, false, "Nurturing Pokémon", Type.BUG, Type.GRASS, 1.2, 20.5, Abilities.SWARM, Abilities.CHLOROPHYLL, Abilities.OVERCOAT, 500, 75, 103, 80, 70, 80, 92, 45, 70, 225, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(Species.LEAVANNY, 5, false, false, false, "Nurturing Pokémon", Type.BUG, Type.GRASS, 1.2, 20.5, Abilities.SWARM, Abilities.CHLOROPHYLL, Abilities.OVERCOAT, 500, 75, 103, 80, 70, 80, 92, 45, 70, 250, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.VENIPEDE, 5, false, false, false, "Centipede Pokémon", Type.BUG, Type.POISON, 0.4, 5.3, Abilities.POISON_POINT, Abilities.SWARM, Abilities.SPEED_BOOST, 260, 30, 45, 59, 30, 39, 57, 255, 50, 52, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.WHIRLIPEDE, 5, false, false, false, "Curlipede Pokémon", Type.BUG, Type.POISON, 1.2, 58.5, Abilities.POISON_POINT, Abilities.SWARM, Abilities.SPEED_BOOST, 360, 40, 55, 99, 40, 79, 47, 120, 50, 126, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.SCOLIPEDE, 5, false, false, false, "Megapede Pokémon", Type.BUG, Type.POISON, 2.5, 200.5, Abilities.POISON_POINT, Abilities.SWARM, Abilities.SPEED_BOOST, 485, 60, 100, 89, 55, 69, 112, 45, 50, 243, GrowthRate.MEDIUM_SLOW, 50, false), @@ -1834,7 +1834,7 @@ export function initSpecies() { new PokemonSpecies(Species.DARUMAKA, 5, false, false, false, "Zen Charm Pokémon", Type.FIRE, null, 0.6, 37.5, Abilities.HUSTLE, Abilities.NONE, Abilities.INNER_FOCUS, 315, 70, 90, 45, 15, 45, 50, 120, 50, 63, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.DARMANITAN, 5, false, false, false, "Blazing Pokémon", Type.FIRE, null, 1.3, 92.9, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.ZEN_MODE, 480, 105, 140, 55, 30, 55, 95, 60, 50, 168, GrowthRate.MEDIUM_SLOW, 50, false, true, new PokemonForm("Standard Mode", "", Type.FIRE, null, 1.3, 92.9, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.ZEN_MODE, 480, 105, 140, 55, 30, 55, 95, 60, 50, 168, false, null, true), - new PokemonForm("Zen Mode", "zen", Type.FIRE, Type.PSYCHIC, 1.3, 92.9, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.ZEN_MODE, 540, 105, 30, 105, 140, 105, 55, 60, 50, 168), + new PokemonForm("Zen Mode", "zen", Type.FIRE, Type.PSYCHIC, 1.3, 92.9, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.ZEN_MODE, 540, 105, 30, 105, 140, 105, 55, 60, 50, 189), ), new PokemonSpecies(Species.MARACTUS, 5, false, false, false, "Cactus Pokémon", Type.GRASS, null, 1, 28, Abilities.WATER_ABSORB, Abilities.CHLOROPHYLL, Abilities.STORM_DRAIN, 461, 75, 86, 67, 106, 67, 60, 255, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.DWEBBLE, 5, false, false, false, "Rock Inn Pokémon", Type.BUG, Type.ROCK, 0.3, 14.5, Abilities.STURDY, Abilities.SHELL_ARMOR, Abilities.WEAK_ARMOR, 325, 50, 65, 85, 35, 35, 55, 190, 50, 65, GrowthRate.MEDIUM_FAST, 50, false), @@ -1897,7 +1897,7 @@ export function initSpecies() { new PokemonSpecies(Species.KLINKLANG, 5, false, false, false, "Gear Pokémon", Type.STEEL, null, 0.6, 81, Abilities.PLUS, Abilities.MINUS, Abilities.CLEAR_BODY, 520, 60, 100, 115, 70, 85, 90, 30, 50, 260, GrowthRate.MEDIUM_SLOW, null, false), new PokemonSpecies(Species.TYNAMO, 5, false, false, false, "EleFish Pokémon", Type.ELECTRIC, null, 0.2, 0.3, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 275, 35, 55, 40, 45, 40, 60, 190, 70, 55, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.EELEKTRIK, 5, false, false, false, "EleFish Pokémon", Type.ELECTRIC, null, 1.2, 22, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 405, 65, 85, 70, 75, 70, 40, 60, 70, 142, GrowthRate.SLOW, 50, false), - new PokemonSpecies(Species.EELEKTROSS, 5, false, false, false, "EleFish Pokémon", Type.ELECTRIC, null, 2.1, 80.5, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 515, 85, 115, 80, 105, 80, 50, 30, 70, 232, GrowthRate.SLOW, 50, false), + new PokemonSpecies(Species.EELEKTROSS, 5, false, false, false, "EleFish Pokémon", Type.ELECTRIC, null, 2.1, 80.5, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 515, 85, 115, 80, 105, 80, 50, 30, 70, 258, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.ELGYEM, 5, false, false, false, "Cerebral Pokémon", Type.PSYCHIC, null, 0.5, 9, Abilities.TELEPATHY, Abilities.SYNCHRONIZE, Abilities.ANALYTIC, 335, 55, 55, 55, 85, 55, 30, 255, 50, 67, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.BEHEEYEM, 5, false, false, false, "Cerebral Pokémon", Type.PSYCHIC, null, 1, 34.5, Abilities.TELEPATHY, Abilities.SYNCHRONIZE, Abilities.ANALYTIC, 485, 75, 75, 75, 125, 95, 40, 90, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.LITWICK, 5, false, false, false, "Candle Pokémon", Type.GHOST, Type.FIRE, 0.3, 3.1, Abilities.FLASH_FIRE, Abilities.FLAME_BODY, Abilities.INFILTRATOR, 275, 50, 30, 55, 65, 55, 20, 190, 50, 55, GrowthRate.MEDIUM_SLOW, 50, false), @@ -1950,16 +1950,16 @@ export function initSpecies() { ), new PokemonSpecies(Species.KYUREM, 5, false, true, false, "Boundary Pokémon", Type.DRAGON, Type.ICE, 3, 325, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 660, 125, 130, 90, 130, 90, 95, 3, 0, 330, GrowthRate.SLOW, null, false, true, new PokemonForm("Normal", "", Type.DRAGON, Type.ICE, 3, 325, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 660, 125, 130, 90, 130, 90, 95, 3, 0, 330, false, null, true), - new PokemonForm("Black", "black", Type.DRAGON, Type.ICE, 3.3, 325, Abilities.TERAVOLT, Abilities.NONE, Abilities.NONE, 700, 125, 170, 100, 120, 90, 95, 3, 0, 330), - new PokemonForm("White", "white", Type.DRAGON, Type.ICE, 3.6, 325, Abilities.TURBOBLAZE, Abilities.NONE, Abilities.NONE, 700, 125, 120, 90, 170, 100, 95, 3, 0, 330), + new PokemonForm("Black", "black", Type.DRAGON, Type.ICE, 3.3, 325, Abilities.TERAVOLT, Abilities.NONE, Abilities.NONE, 700, 125, 170, 100, 120, 90, 95, 3, 0, 350), + new PokemonForm("White", "white", Type.DRAGON, Type.ICE, 3.6, 325, Abilities.TURBOBLAZE, Abilities.NONE, Abilities.NONE, 700, 125, 120, 90, 170, 100, 95, 3, 0, 350), ), new PokemonSpecies(Species.KELDEO, 5, false, false, true, "Colt Pokémon", Type.WATER, Type.FIGHTING, 1.4, 48.5, Abilities.JUSTIFIED, Abilities.NONE, Abilities.NONE, 580, 91, 72, 90, 129, 90, 108, 3, 35, 290, GrowthRate.SLOW, null, false, true, new PokemonForm("Ordinary Form", "ordinary", Type.WATER, Type.FIGHTING, 1.4, 48.5, Abilities.JUSTIFIED, Abilities.NONE, Abilities.NONE, 580, 91, 72, 90, 129, 90, 108, 3, 35, 290, false, null, true), new PokemonForm("Resolute", "resolute", Type.WATER, Type.FIGHTING, 1.4, 48.5, Abilities.JUSTIFIED, Abilities.NONE, Abilities.NONE, 580, 91, 72, 90, 129, 90, 108, 3, 35, 290), ), - new PokemonSpecies(Species.MELOETTA, 5, false, false, true, "Melody Pokémon", Type.NORMAL, Type.PSYCHIC, 0.6, 6.5, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 77, 77, 128, 128, 90, 3, 100, 270, GrowthRate.SLOW, null, false, true, - new PokemonForm("Aria Forme", "aria", Type.NORMAL, Type.PSYCHIC, 0.6, 6.5, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 77, 77, 128, 128, 90, 3, 100, 270, false, null, true), - new PokemonForm("Pirouette Forme", "pirouette", Type.NORMAL, Type.FIGHTING, 0.6, 6.5, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 128, 90, 77, 77, 128, 3, 100, 270, false, null, true), + new PokemonSpecies(Species.MELOETTA, 5, false, false, true, "Melody Pokémon", Type.NORMAL, Type.PSYCHIC, 0.6, 6.5, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 77, 77, 128, 128, 90, 3, 100, 300, GrowthRate.SLOW, null, false, true, + new PokemonForm("Aria Forme", "aria", Type.NORMAL, Type.PSYCHIC, 0.6, 6.5, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 77, 77, 128, 128, 90, 3, 100, 300, false, null, true), + new PokemonForm("Pirouette Forme", "pirouette", Type.NORMAL, Type.FIGHTING, 0.6, 6.5, Abilities.SERENE_GRACE, Abilities.NONE, Abilities.NONE, 600, 100, 128, 90, 77, 77, 128, 3, 100, 300, false, null, true), ), new PokemonSpecies(Species.GENESECT, 5, false, false, true, "Paleozoic Pokémon", Type.BUG, Type.STEEL, 1.5, 82.5, Abilities.DOWNLOAD, Abilities.NONE, Abilities.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300, GrowthRate.SLOW, null, false, true, new PokemonForm("Normal", "", Type.BUG, Type.STEEL, 1.5, 82.5, Abilities.DOWNLOAD, Abilities.NONE, Abilities.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300, false, null, true), @@ -1970,10 +1970,10 @@ export function initSpecies() { ), new PokemonSpecies(Species.CHESPIN, 6, false, false, false, "Spiny Nut Pokémon", Type.GRASS, null, 0.4, 9, Abilities.OVERGROW, Abilities.NONE, Abilities.BULLETPROOF, 313, 56, 61, 65, 48, 45, 38, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.QUILLADIN, 6, false, false, false, "Spiny Armor Pokémon", Type.GRASS, null, 0.7, 29, Abilities.OVERGROW, Abilities.NONE, Abilities.BULLETPROOF, 405, 61, 78, 95, 56, 58, 57, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(Species.CHESNAUGHT, 6, false, false, false, "Spiny Armor Pokémon", Type.GRASS, Type.FIGHTING, 1.6, 90, Abilities.OVERGROW, Abilities.NONE, Abilities.BULLETPROOF, 530, 88, 107, 122, 74, 75, 64, 45, 70, 239, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(Species.CHESNAUGHT, 6, false, false, false, "Spiny Armor Pokémon", Type.GRASS, Type.FIGHTING, 1.6, 90, Abilities.OVERGROW, Abilities.NONE, Abilities.BULLETPROOF, 530, 88, 107, 122, 74, 75, 64, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.FENNEKIN, 6, false, false, false, "Fox Pokémon", Type.FIRE, null, 0.4, 9.4, Abilities.BLAZE, Abilities.NONE, Abilities.MAGICIAN, 307, 40, 45, 40, 62, 60, 60, 45, 70, 61, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.BRAIXEN, 6, false, false, false, "Fox Pokémon", Type.FIRE, null, 1, 14.5, Abilities.BLAZE, Abilities.NONE, Abilities.MAGICIAN, 409, 59, 59, 58, 90, 70, 73, 45, 70, 143, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(Species.DELPHOX, 6, false, false, false, "Fox Pokémon", Type.FIRE, Type.PSYCHIC, 1.5, 39, Abilities.BLAZE, Abilities.NONE, Abilities.MAGICIAN, 534, 75, 69, 72, 114, 100, 104, 45, 70, 240, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(Species.DELPHOX, 6, false, false, false, "Fox Pokémon", Type.FIRE, Type.PSYCHIC, 1.5, 39, Abilities.BLAZE, Abilities.NONE, Abilities.MAGICIAN, 534, 75, 69, 72, 114, 100, 104, 45, 70, 267, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.FROAKIE, 6, false, false, false, "Bubble Frog Pokémon", Type.WATER, null, 0.3, 7, Abilities.TORRENT, Abilities.NONE, Abilities.PROTEAN, 314, 41, 56, 40, 62, 44, 71, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false, false, new PokemonForm("Normal", "", Type.WATER, null, 0.3, 7, Abilities.TORRENT, Abilities.NONE, Abilities.PROTEAN, 314, 41, 56, 40, 62, 44, 71, 45, 70, 63, false, null, true), new PokemonForm("Battle Bond", "battle-bond", Type.WATER, null, 0.3, 7, Abilities.TORRENT, Abilities.NONE, Abilities.PROTEAN, 314, 41, 56, 40, 62, 44, 71, 45, 70, 63, false, "", true), @@ -1982,10 +1982,10 @@ export function initSpecies() { new PokemonForm("Normal", "", Type.WATER, null, 0.6, 10.9, Abilities.TORRENT, Abilities.NONE, Abilities.PROTEAN, 405, 54, 63, 52, 83, 56, 97, 45, 70, 142, false, null, true), new PokemonForm("Battle Bond", "battle-bond", Type.WATER, null, 0.6, 10.9, Abilities.TORRENT, Abilities.NONE, Abilities.PROTEAN, 405, 54, 63, 52, 83, 56, 97, 45, 70, 142, false, "", true), ), - new PokemonSpecies(Species.GRENINJA, 6, false, false, false, "Ninja Pokémon", Type.WATER, Type.DARK, 1.5, 40, Abilities.TORRENT, Abilities.NONE, Abilities.PROTEAN, 530, 72, 95, 67, 103, 71, 122, 45, 70, 239, GrowthRate.MEDIUM_SLOW, 87.5, false, false, - new PokemonForm("Normal", "", Type.WATER, Type.DARK, 1.5, 40, Abilities.TORRENT, Abilities.NONE, Abilities.PROTEAN, 530, 72, 95, 67, 103, 71, 122, 45, 70, 239, false, null, true), - new PokemonForm("Battle Bond", "battle-bond", Type.WATER, Type.DARK, 1.5, 40, Abilities.BATTLE_BOND, Abilities.NONE, Abilities.BATTLE_BOND, 530, 72, 95, 67, 103, 71, 122, 45, 70, 239, false, "", true), - new PokemonForm("Ash", "ash", Type.WATER, Type.DARK, 1.5, 40, Abilities.BATTLE_BOND, Abilities.NONE, Abilities.BATTLE_BOND, 640, 72, 145, 67, 153, 71, 132, 45, 70, 239), + new PokemonSpecies(Species.GRENINJA, 6, false, false, false, "Ninja Pokémon", Type.WATER, Type.DARK, 1.5, 40, Abilities.TORRENT, Abilities.NONE, Abilities.PROTEAN, 530, 72, 95, 67, 103, 71, 122, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, false, + new PokemonForm("Normal", "", Type.WATER, Type.DARK, 1.5, 40, Abilities.TORRENT, Abilities.NONE, Abilities.PROTEAN, 530, 72, 95, 67, 103, 71, 122, 45, 70, 265, false, null, true), + new PokemonForm("Battle Bond", "battle-bond", Type.WATER, Type.DARK, 1.5, 40, Abilities.BATTLE_BOND, Abilities.NONE, Abilities.BATTLE_BOND, 530, 72, 95, 67, 103, 71, 122, 45, 70, 265, false, "", true), + new PokemonForm("Ash", "ash", Type.WATER, Type.DARK, 1.5, 40, Abilities.BATTLE_BOND, Abilities.NONE, Abilities.BATTLE_BOND, 640, 72, 145, 67, 153, 71, 132, 45, 70, 265), ), new PokemonSpecies(Species.BUNNELBY, 6, false, false, false, "Digging Pokémon", Type.NORMAL, null, 0.4, 5, Abilities.PICKUP, Abilities.CHEEK_POUCH, Abilities.HUGE_POWER, 237, 38, 36, 38, 32, 36, 57, 255, 50, 47, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.DIGGERSBY, 6, false, false, false, "Digging Pokémon", Type.NORMAL, Type.GROUND, 1, 42.4, Abilities.PICKUP, Abilities.CHEEK_POUCH, Abilities.HUGE_POWER, 423, 85, 56, 77, 50, 77, 78, 127, 50, 148, GrowthRate.MEDIUM_FAST, 50, false), @@ -2036,27 +2036,27 @@ export function initSpecies() { new PokemonForm("Fancy Pattern", "fancy", Type.BUG, null, 0.3, 8.4, Abilities.SHED_SKIN, Abilities.NONE, Abilities.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), new PokemonForm("Poké Ball Pattern", "poke-ball", Type.BUG, null, 0.3, 8.4, Abilities.SHED_SKIN, Abilities.NONE, Abilities.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), ), - new PokemonSpecies(Species.VIVILLON, 6, false, false, false, "Scale Pokémon", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Meadow Pattern", "meadow", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Icy Snow Pattern", "icy-snow", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Polar Pattern", "polar", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Tundra Pattern", "tundra", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Continental Pattern", "continental", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Garden Pattern", "garden", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Elegant Pattern", "elegant", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Modern Pattern", "modern", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Marine Pattern", "marine", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Archipelago Pattern", "archipelago", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("High Plains Pattern", "high-plains", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Sandstorm Pattern", "sandstorm", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("River Pattern", "river", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Monsoon Pattern", "monsoon", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Savanna Pattern", "savanna", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Sun Pattern", "sun", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Ocean Pattern", "ocean", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Jungle Pattern", "jungle", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Fancy Pattern", "fancy", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), - new PokemonForm("Poké Ball Pattern", "poke-ball", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 185, false, null, true), + new PokemonSpecies(Species.VIVILLON, 6, false, false, false, "Scale Pokémon", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Meadow Pattern", "meadow", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Icy Snow Pattern", "icy-snow", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Polar Pattern", "polar", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Tundra Pattern", "tundra", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Continental Pattern", "continental", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Garden Pattern", "garden", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Elegant Pattern", "elegant", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Modern Pattern", "modern", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Marine Pattern", "marine", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Archipelago Pattern", "archipelago", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("High Plains Pattern", "high-plains", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Sandstorm Pattern", "sandstorm", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("River Pattern", "river", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Monsoon Pattern", "monsoon", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Savanna Pattern", "savanna", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Sun Pattern", "sun", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Ocean Pattern", "ocean", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Jungle Pattern", "jungle", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Fancy Pattern", "fancy", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Poké Ball Pattern", "poke-ball", Type.BUG, Type.FLYING, 1.2, 17, Abilities.SHIELD_DUST, Abilities.COMPOUND_EYES, Abilities.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), ), new PokemonSpecies(Species.LITLEO, 6, false, false, false, "Lion Cub Pokémon", Type.FIRE, Type.NORMAL, 0.6, 13.5, Abilities.RIVALRY, Abilities.UNNERVE, Abilities.MOXIE, 369, 62, 50, 58, 73, 54, 72, 220, 70, 74, GrowthRate.MEDIUM_SLOW, 12.5, false), new PokemonSpecies(Species.PYROAR, 6, false, false, false, "Royal Pokémon", Type.FIRE, Type.NORMAL, 1.5, 81.5, Abilities.RIVALRY, Abilities.UNNERVE, Abilities.MOXIE, 507, 86, 68, 72, 109, 66, 106, 65, 70, 177, GrowthRate.MEDIUM_SLOW, 12.5, true), @@ -2074,12 +2074,12 @@ export function initSpecies() { new PokemonForm("Blue Flower", "blue", Type.FAIRY, null, 0.2, 0.9, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true), new PokemonForm("White Flower", "white", Type.FAIRY, null, 0.2, 0.9, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true), ), - new PokemonSpecies(Species.FLORGES, 6, false, false, false, "Garden Pokémon", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 248, GrowthRate.MEDIUM_FAST, 0, false, false, - new PokemonForm("Red Flower", "red", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 248, false, null, true), - new PokemonForm("Yellow Flower", "yellow", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 248, false, null, true), - new PokemonForm("Orange Flower", "orange", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 248, false, null, true), - new PokemonForm("Blue Flower", "blue", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 248, false, null, true), - new PokemonForm("White Flower", "white", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 248, false, null, true), + new PokemonSpecies(Species.FLORGES, 6, false, false, false, "Garden Pokémon", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, GrowthRate.MEDIUM_FAST, 0, false, false, + new PokemonForm("Red Flower", "red", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), + new PokemonForm("Yellow Flower", "yellow", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), + new PokemonForm("Orange Flower", "orange", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), + new PokemonForm("Blue Flower", "blue", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), + new PokemonForm("White Flower", "white", Type.FAIRY, null, 1.1, 10, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), ), new PokemonSpecies(Species.SKIDDO, 6, false, false, false, "Mount Pokémon", Type.GRASS, null, 0.9, 31, Abilities.SAP_SIPPER, Abilities.NONE, Abilities.GRASS_PELT, 350, 66, 65, 48, 62, 57, 52, 200, 70, 70, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.GOGOAT, 6, false, false, false, "Mount Pokémon", Type.GRASS, null, 1.7, 91, Abilities.SAP_SIPPER, Abilities.NONE, Abilities.GRASS_PELT, 531, 123, 100, 62, 97, 81, 68, 45, 70, 186, GrowthRate.MEDIUM_FAST, 50, false), @@ -2159,19 +2159,19 @@ export function initSpecies() { new PokemonSpecies(Species.YVELTAL, 6, false, true, false, "Destruction Pokémon", Type.DARK, Type.FLYING, 5.8, 203, Abilities.DARK_AURA, Abilities.NONE, Abilities.NONE, 680, 126, 131, 95, 131, 98, 99, 45, 0, 340, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.ZYGARDE, 6, false, true, false, "Order Pokémon", Type.DRAGON, Type.GROUND, 5, 305, Abilities.AURA_BREAK, Abilities.NONE, Abilities.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, GrowthRate.SLOW, null, false, false, new PokemonForm("50% Forme", "50", Type.DRAGON, Type.GROUND, 5, 305, Abilities.AURA_BREAK, Abilities.NONE, Abilities.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, false, "", true), - 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("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, 243, 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 (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 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, 243, false, "10", true), + 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, 354), + 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, 354, 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), new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.ROCK, Type.FAIRY, 1.1, 27.8, Abilities.MAGIC_BOUNCE, Abilities.NONE, Abilities.NONE, 700, 50, 160, 110, 160, 110, 110, 3, 50, 300), ), - new PokemonSpecies(Species.HOOPA, 6, false, false, true, "Mischief Pokémon", Type.PSYCHIC, Type.GHOST, 0.5, 9, Abilities.MAGICIAN, Abilities.NONE, Abilities.NONE, 600, 80, 110, 60, 150, 130, 70, 3, 100, 270, GrowthRate.SLOW, null, false, false, - new PokemonForm("Hoopa Confined", "", Type.PSYCHIC, Type.GHOST, 0.5, 9, Abilities.MAGICIAN, Abilities.NONE, Abilities.NONE, 600, 80, 110, 60, 150, 130, 70, 3, 100, 270, false, null, true), - new PokemonForm("Hoopa Unbound", "unbound", Type.PSYCHIC, Type.DARK, 6.5, 490, Abilities.MAGICIAN, Abilities.NONE, Abilities.NONE, 680, 80, 160, 60, 170, 130, 80, 3, 100, 270), + new PokemonSpecies(Species.HOOPA, 6, false, false, true, "Mischief Pokémon", Type.PSYCHIC, Type.GHOST, 0.5, 9, Abilities.MAGICIAN, Abilities.NONE, Abilities.NONE, 600, 80, 110, 60, 150, 130, 70, 3, 100, 300, GrowthRate.SLOW, null, false, false, + new PokemonForm("Hoopa Confined", "", Type.PSYCHIC, Type.GHOST, 0.5, 9, Abilities.MAGICIAN, Abilities.NONE, Abilities.NONE, 600, 80, 110, 60, 150, 130, 70, 3, 100, 300, false, null, true), + new PokemonForm("Hoopa Unbound", "unbound", Type.PSYCHIC, Type.DARK, 6.5, 490, Abilities.MAGICIAN, Abilities.NONE, Abilities.NONE, 680, 80, 160, 60, 170, 130, 80, 3, 100, 340), ), new PokemonSpecies(Species.VOLCANION, 6, false, false, true, "Steam Pokémon", Type.FIRE, Type.WATER, 1.7, 195, Abilities.WATER_ABSORB, Abilities.NONE, Abilities.NONE, 600, 80, 110, 120, 130, 90, 70, 3, 100, 300, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.ROWLET, 7, false, false, false, "Grass Quill Pokémon", Type.GRASS, Type.FLYING, 0.3, 1.5, Abilities.OVERGROW, Abilities.NONE, Abilities.LONG_REACH, 320, 68, 55, 55, 50, 50, 42, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), @@ -2185,7 +2185,7 @@ export function initSpecies() { new PokemonSpecies(Species.PRIMARINA, 7, false, false, false, "Soloist Pokémon", Type.WATER, Type.FAIRY, 1.8, 44, Abilities.TORRENT, Abilities.NONE, Abilities.LIQUID_VOICE, 530, 80, 74, 74, 126, 116, 60, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.PIKIPEK, 7, false, false, false, "Woodpecker Pokémon", Type.NORMAL, Type.FLYING, 0.3, 1.2, Abilities.KEEN_EYE, Abilities.SKILL_LINK, Abilities.PICKUP, 265, 35, 75, 30, 30, 30, 65, 255, 70, 53, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.TRUMBEAK, 7, false, false, false, "Bugle Beak Pokémon", Type.NORMAL, Type.FLYING, 0.6, 14.8, Abilities.KEEN_EYE, Abilities.SKILL_LINK, Abilities.PICKUP, 355, 55, 85, 50, 40, 50, 75, 120, 70, 124, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(Species.TOUCANNON, 7, false, false, false, "Cannon Pokémon", Type.NORMAL, Type.FLYING, 1.1, 26, Abilities.KEEN_EYE, Abilities.SKILL_LINK, Abilities.SHEER_FORCE, 485, 80, 120, 75, 75, 75, 60, 45, 70, 218, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(Species.TOUCANNON, 7, false, false, false, "Cannon Pokémon", Type.NORMAL, Type.FLYING, 1.1, 26, Abilities.KEEN_EYE, Abilities.SKILL_LINK, Abilities.SHEER_FORCE, 485, 80, 120, 75, 75, 75, 60, 45, 70, 243, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.YUNGOOS, 7, false, false, false, "Loitering Pokémon", Type.NORMAL, null, 0.4, 6, Abilities.STAKEOUT, Abilities.STRONG_JAW, Abilities.ADAPTABILITY, 253, 48, 70, 30, 30, 30, 45, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.GUMSHOOS, 7, false, false, false, "Stakeout Pokémon", Type.NORMAL, null, 0.7, 14.2, Abilities.STAKEOUT, Abilities.STRONG_JAW, Abilities.ADAPTABILITY, 418, 88, 110, 60, 55, 60, 45, 127, 70, 146, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.GRUBBIN, 7, false, false, false, "Larva Pokémon", Type.BUG, null, 0.4, 4.4, Abilities.SWARM, Abilities.NONE, Abilities.NONE, 300, 47, 62, 45, 55, 45, 46, 255, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), @@ -2212,7 +2212,7 @@ export function initSpecies() { ), new PokemonSpecies(Species.WISHIWASHI, 7, false, false, false, "Small Fry Pokémon", Type.WATER, null, 0.2, 0.3, Abilities.SCHOOLING, Abilities.NONE, Abilities.NONE, 175, 45, 20, 20, 25, 25, 40, 60, 50, 61, GrowthRate.FAST, 50, false, false, new PokemonForm("Solo Form", "", Type.WATER, null, 0.2, 0.3, Abilities.SCHOOLING, Abilities.NONE, Abilities.NONE, 175, 45, 20, 20, 25, 25, 40, 60, 50, 61, false, null, true), - new PokemonForm("School", "school", Type.WATER, null, 8.2, 78.6, Abilities.SCHOOLING, Abilities.NONE, Abilities.NONE, 620, 45, 140, 130, 140, 135, 30, 60, 50, 61), + new PokemonForm("School", "school", Type.WATER, null, 8.2, 78.6, Abilities.SCHOOLING, Abilities.NONE, Abilities.NONE, 620, 45, 140, 130, 140, 135, 30, 60, 50, 217), ), new PokemonSpecies(Species.MAREANIE, 7, false, false, false, "Brutal Star Pokémon", Type.POISON, Type.WATER, 0.4, 8, Abilities.MERCILESS, Abilities.LIMBER, Abilities.REGENERATOR, 305, 50, 53, 62, 43, 52, 45, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.TOXAPEX, 7, false, false, false, "Brutal Star Pokémon", Type.POISON, Type.WATER, 0.7, 14.5, Abilities.MERCILESS, Abilities.LIMBER, Abilities.REGENERATOR, 495, 50, 63, 152, 53, 142, 35, 75, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), @@ -2268,13 +2268,13 @@ export function initSpecies() { new PokemonForm("Blue Meteor Form", "blue-meteor", Type.ROCK, Type.FLYING, 0.3, 40, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, "", true), new PokemonForm("Indigo Meteor Form", "indigo-meteor", Type.ROCK, Type.FLYING, 0.3, 40, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, "", true), new PokemonForm("Violet Meteor Form", "violet-meteor", Type.ROCK, Type.FLYING, 0.3, 40, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, "", true), - new PokemonForm("Red Core Form", "red", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 154, false, null, true), - new PokemonForm("Orange Core Form", "orange", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 154, false, null, true), - new PokemonForm("Yellow Core Form", "yellow", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 154, false, null, true), - new PokemonForm("Green Core Form", "green", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 154, false, null, true), - new PokemonForm("Blue Core Form", "blue", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 154, false, null, true), - new PokemonForm("Indigo Core Form", "indigo", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 154, false, null, true), - new PokemonForm("Violet Core Form", "violet", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 154, false, null, true), + new PokemonForm("Red Core Form", "red", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Orange Core Form", "orange", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Yellow Core Form", "yellow", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Green Core Form", "green", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Blue Core Form", "blue", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Indigo Core Form", "indigo", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Violet Core Form", "violet", Type.ROCK, Type.FLYING, 0.3, 0.3, Abilities.SHIELDS_DOWN, Abilities.NONE, Abilities.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), ), new PokemonSpecies(Species.KOMALA, 7, false, false, false, "Drowsing Pokémon", Type.NORMAL, null, 0.4, 19.9, Abilities.COMATOSE, Abilities.NONE, Abilities.NONE, 480, 65, 115, 65, 75, 95, 65, 45, 70, 168, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.TURTONATOR, 7, false, false, false, "Blast Turtle Pokémon", Type.FIRE, Type.DRAGON, 2, 212, Abilities.SHELL_ARMOR, Abilities.NONE, Abilities.NONE, 485, 60, 78, 135, 91, 85, 36, 70, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), @@ -2306,9 +2306,9 @@ export function initSpecies() { new PokemonSpecies(Species.GUZZLORD, 7, true, false, false, "Junkivore Pokémon", Type.DARK, Type.DRAGON, 5.5, 888, Abilities.BEAST_BOOST, Abilities.NONE, Abilities.NONE, 570, 223, 101, 53, 97, 53, 43, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.NECROZMA, 7, false, true, false, "Prism Pokémon", Type.PSYCHIC, null, 2.4, 230, Abilities.PRISM_ARMOR, Abilities.NONE, Abilities.NONE, 600, 97, 107, 101, 127, 89, 79, 255, 0, 300, GrowthRate.SLOW, null, false, false, new PokemonForm("Normal", "", Type.PSYCHIC, null, 2.4, 230, Abilities.PRISM_ARMOR, Abilities.NONE, Abilities.NONE, 600, 97, 107, 101, 127, 89, 79, 255, 0, 300, false, null, true), - new PokemonForm("Dusk Mane", "dusk-mane", Type.PSYCHIC, Type.STEEL, 3.8, 460, Abilities.PRISM_ARMOR, Abilities.NONE, Abilities.NONE, 680, 97, 157, 127, 113, 109, 77, 255, 0, 300), - new PokemonForm("Dawn Wings", "dawn-wings", Type.PSYCHIC, Type.GHOST, 4.2, 350, Abilities.PRISM_ARMOR, Abilities.NONE, Abilities.NONE, 680, 97, 113, 109, 157, 127, 77, 255, 0, 300), - new PokemonForm("Ultra", "ultra", Type.PSYCHIC, Type.DRAGON, 7.5, 230, Abilities.NEUROFORCE, Abilities.NONE, Abilities.NONE, 754, 97, 167, 97, 167, 97, 129, 255, 0, 300), + new PokemonForm("Dusk Mane", "dusk-mane", Type.PSYCHIC, Type.STEEL, 3.8, 460, Abilities.PRISM_ARMOR, Abilities.NONE, Abilities.NONE, 680, 97, 157, 127, 113, 109, 77, 255, 0, 340), + new PokemonForm("Dawn Wings", "dawn-wings", Type.PSYCHIC, Type.GHOST, 4.2, 350, Abilities.PRISM_ARMOR, Abilities.NONE, Abilities.NONE, 680, 97, 113, 109, 157, 127, 77, 255, 0, 340), + new PokemonForm("Ultra", "ultra", Type.PSYCHIC, Type.DRAGON, 7.5, 230, Abilities.NEUROFORCE, Abilities.NONE, Abilities.NONE, 754, 97, 167, 97, 167, 97, 129, 255, 0, 377), ), new PokemonSpecies(Species.MAGEARNA, 7, false, false, true, "Artificial Pokémon", Type.STEEL, Type.FAIRY, 1, 80.5, Abilities.SOUL_HEART, Abilities.NONE, Abilities.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, GrowthRate.SLOW, null, false, false, new PokemonForm("Normal", "", Type.STEEL, Type.FAIRY, 1, 80.5, Abilities.SOUL_HEART, Abilities.NONE, Abilities.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, false, null, true), @@ -2487,11 +2487,11 @@ export function initSpecies() { new PokemonSpecies(Species.DRAGAPULT, 8, false, false, false, "Stealth Pokémon", Type.DRAGON, Type.GHOST, 3, 50, Abilities.CLEAR_BODY, Abilities.INFILTRATOR, Abilities.CURSED_BODY, 600, 88, 120, 75, 100, 75, 142, 45, 50, 300, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.ZACIAN, 8, false, true, false, "Warrior Pokémon", Type.FAIRY, null, 2.8, 110, Abilities.INTREPID_SWORD, Abilities.NONE, Abilities.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, GrowthRate.SLOW, null, false, false, new PokemonForm("Hero of Many Battles", "hero-of-many-battles", Type.FAIRY, null, 2.8, 110, Abilities.INTREPID_SWORD, Abilities.NONE, Abilities.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, false, "", true), - new PokemonForm("Crowned", "crowned", Type.FAIRY, Type.STEEL, 2.8, 355, Abilities.INTREPID_SWORD, Abilities.NONE, Abilities.NONE, 700, 92, 150, 115, 80, 115, 148, 10, 0, 335), + new PokemonForm("Crowned", "crowned", Type.FAIRY, Type.STEEL, 2.8, 355, Abilities.INTREPID_SWORD, Abilities.NONE, Abilities.NONE, 700, 92, 150, 115, 80, 115, 148, 10, 0, 360), ), new PokemonSpecies(Species.ZAMAZENTA, 8, false, true, false, "Warrior Pokémon", Type.FIGHTING, null, 2.9, 210, Abilities.DAUNTLESS_SHIELD, Abilities.NONE, Abilities.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, GrowthRate.SLOW, null, false, false, new PokemonForm("Hero of Many Battles", "hero-of-many-battles", Type.FIGHTING, null, 2.9, 210, Abilities.DAUNTLESS_SHIELD, Abilities.NONE, Abilities.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, false, "", true), - new PokemonForm("Crowned", "crowned", Type.FIGHTING, Type.STEEL, 2.9, 785, Abilities.DAUNTLESS_SHIELD, Abilities.NONE, Abilities.NONE, 700, 92, 120, 140, 80, 140, 128, 10, 0, 335), + new PokemonForm("Crowned", "crowned", Type.FIGHTING, Type.STEEL, 2.9, 785, Abilities.DAUNTLESS_SHIELD, Abilities.NONE, Abilities.NONE, 700, 92, 120, 140, 80, 140, 128, 10, 0, 360), ), new PokemonSpecies(Species.ETERNATUS, 8, false, true, false, "Gigantic Pokémon", Type.POISON, Type.DRAGON, 20, 950, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 690, 140, 85, 95, 145, 95, 130, 255, 0, 345, GrowthRate.SLOW, null, false, true, new PokemonForm("Normal", "", Type.POISON, Type.DRAGON, 20, 950, Abilities.PRESSURE, Abilities.NONE, Abilities.NONE, 690, 140, 85, 95, 145, 95, 130, 255, 0, 345, false, null, true), @@ -2514,8 +2514,8 @@ export function initSpecies() { new PokemonSpecies(Species.SPECTRIER, 8, true, false, false, "Swift Horse Pokémon", Type.GHOST, null, 2, 44.5, Abilities.GRIM_NEIGH, Abilities.NONE, Abilities.NONE, 580, 100, 65, 60, 145, 80, 130, 3, 35, 290, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.CALYREX, 8, false, true, false, "King Pokémon", Type.PSYCHIC, Type.GRASS, 1.1, 7.7, Abilities.UNNERVE, Abilities.NONE, Abilities.NONE, 500, 100, 80, 80, 80, 80, 80, 3, 100, 250, GrowthRate.SLOW, null, false, true, new PokemonForm("Normal", "", Type.PSYCHIC, Type.GRASS, 1.1, 7.7, Abilities.UNNERVE, Abilities.NONE, Abilities.NONE, 500, 100, 80, 80, 80, 80, 80, 3, 100, 250, false, null, true), - new PokemonForm("Ice", "ice", Type.PSYCHIC, Type.ICE, 2.4, 809.1, Abilities.AS_ONE_GLASTRIER, Abilities.NONE, Abilities.NONE, 680, 100, 165, 150, 85, 130, 50, 3, 100, 250), - new PokemonForm("Shadow", "shadow", Type.PSYCHIC, Type.GHOST, 2.4, 53.6, Abilities.AS_ONE_SPECTRIER, Abilities.NONE, Abilities.NONE, 680, 100, 85, 80, 165, 100, 150, 3, 100, 250), + new PokemonForm("Ice", "ice", Type.PSYCHIC, Type.ICE, 2.4, 809.1, Abilities.AS_ONE_GLASTRIER, Abilities.NONE, Abilities.NONE, 680, 100, 165, 150, 85, 130, 50, 3, 100, 340), + new PokemonForm("Shadow", "shadow", Type.PSYCHIC, Type.GHOST, 2.4, 53.6, Abilities.AS_ONE_SPECTRIER, Abilities.NONE, Abilities.NONE, 680, 100, 85, 80, 165, 100, 150, 3, 100, 340), ), new PokemonSpecies(Species.WYRDEER, 8, false, false, false, "Big Horn Pokémon", Type.NORMAL, Type.PSYCHIC, 1.8, 95.1, Abilities.INTIMIDATE, Abilities.FRISK, Abilities.SAP_SIPPER, 525, 103, 105, 72, 105, 75, 65, 135, 50, 263, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.KLEAVOR, 8, false, false, false, "Axe Pokémon", Type.BUG, Type.ROCK, 1.8, 89, Abilities.SWARM, Abilities.SHEER_FORCE, Abilities.SHARPNESS, 500, 70, 135, 95, 45, 70, 85, 115, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), @@ -2700,8 +2700,8 @@ export function initSpecies() { new PokemonSpecies(Species.IRON_CROWN, 9, false, false, false, "Paradox Pokémon", Type.STEEL, Type.PSYCHIC, 1.6, 156, Abilities.QUARK_DRIVE, Abilities.NONE, Abilities.NONE, 590, 90, 72, 100, 122, 108, 98, 10, 0, 295, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.TERAPAGOS, 9, false, true, false, "Tera Pokémon", Type.NORMAL, null, 0.2, 6.5, Abilities.TERA_SHIFT, Abilities.NONE, Abilities.NONE, 450, 90, 65, 85, 65, 85, 60, 5, 50, 90, GrowthRate.SLOW, 50, false, false, new PokemonForm("Normal Form", "", Type.NORMAL, null, 0.2, 6.5, Abilities.TERA_SHIFT, Abilities.NONE, Abilities.NONE, 450, 90, 65, 85, 65, 85, 60, 5, 50, 90, false, null, true), - new PokemonForm("Terastal Form", "terastal", Type.NORMAL, null, 0.3, 16, Abilities.TERA_SHELL, Abilities.NONE, Abilities.NONE, 600, 95, 95, 110, 105, 110, 85, 5, 50, 90), - new PokemonForm("Stellar Form", "stellar", Type.NORMAL, null, 1.7, 77, Abilities.TERAFORM_ZERO, Abilities.NONE, Abilities.NONE, 700, 160, 105, 110, 130, 110, 85, 5, 50, 90), + new PokemonForm("Terastal Form", "terastal", Type.NORMAL, null, 0.3, 16, Abilities.TERA_SHELL, Abilities.NONE, Abilities.NONE, 600, 95, 95, 110, 105, 110, 85, 5, 50, 120), + new PokemonForm("Stellar Form", "stellar", Type.NORMAL, null, 1.7, 77, Abilities.TERAFORM_ZERO, Abilities.NONE, Abilities.NONE, 700, 160, 105, 110, 130, 110, 85, 5, 50, 140), ), new PokemonSpecies(Species.PECHARUNT, 9, false, false, true, "Subjugation Pokémon", Type.POISON, Type.GHOST, 0.3, 0.3, Abilities.POISON_PUPPETEER, Abilities.NONE, Abilities.NONE, 600, 88, 88, 160, 88, 88, 88, 3, 0, 300, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.ALOLA_RATTATA, 7, false, false, false, "Mouse Pokémon", Type.DARK, Type.NORMAL, 0.3, 3.8, Abilities.GLUTTONY, Abilities.HUSTLE, Abilities.THICK_FAT, 253, 30, 56, 35, 25, 35, 72, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, false), @@ -2722,7 +2722,7 @@ export function initSpecies() { new PokemonSpecies(Species.ALOLA_MUK, 7, false, false, false, "Sludge Pokémon", Type.POISON, Type.DARK, 1, 52, Abilities.POISON_TOUCH, Abilities.GLUTTONY, Abilities.POWER_OF_ALCHEMY, 500, 105, 105, 75, 65, 100, 50, 75, 70, 175, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.ALOLA_EXEGGUTOR, 7, false, false, false, "Coconut Pokémon", Type.GRASS, Type.DRAGON, 10.9, 415.6, Abilities.FRISK, Abilities.NONE, Abilities.HARVEST, 530, 95, 105, 85, 125, 75, 45, 45, 50, 186, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.ALOLA_MAROWAK, 7, false, false, false, "Bone Keeper Pokémon", Type.FIRE, Type.GHOST, 1, 34, Abilities.CURSED_BODY, Abilities.LIGHTNING_ROD, Abilities.ROCK_HEAD, 425, 60, 80, 110, 50, 80, 45, 75, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(Species.ETERNAL_FLOETTE, 6, true, false, false, "Single Bloom Pokémon", Type.FAIRY, null, 0.2, 0.9, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 551, 74, 65, 67, 125, 128, 92, 120, 70, 130, GrowthRate.MEDIUM_FAST, 0, false), //Marked as Sub-Legend, for casing purposes + new PokemonSpecies(Species.ETERNAL_FLOETTE, 6, true, false, false, "Single Bloom Pokémon", Type.FAIRY, null, 0.2, 0.9, Abilities.FLOWER_VEIL, Abilities.NONE, Abilities.SYMBIOSIS, 551, 74, 65, 67, 125, 128, 92, 120, 70, 243, GrowthRate.MEDIUM_FAST, 0, false), //Marked as Sub-Legend, for casing purposes new PokemonSpecies(Species.GALAR_MEOWTH, 8, false, false, false, "Scratch Cat Pokémon", Type.STEEL, null, 0.4, 7.5, Abilities.PICKUP, Abilities.TOUGH_CLAWS, Abilities.UNNERVE, 290, 50, 65, 55, 40, 40, 40, 255, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.GALAR_PONYTA, 8, false, false, false, "Fire Horse Pokémon", Type.PSYCHIC, null, 0.8, 24, Abilities.RUN_AWAY, Abilities.PASTEL_VEIL, Abilities.ANTICIPATION, 410, 50, 85, 55, 65, 65, 90, 190, 50, 82, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.GALAR_RAPIDASH, 8, false, false, false, "Fire Horse Pokémon", Type.PSYCHIC, Type.FAIRY, 1.7, 80, Abilities.RUN_AWAY, Abilities.PASTEL_VEIL, Abilities.ANTICIPATION, 500, 65, 100, 70, 80, 80, 105, 60, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), @@ -2767,7 +2767,7 @@ export function initSpecies() { new PokemonForm("Aqua Breed", "aqua", Type.FIGHTING, Type.WATER, 1.4, 110, Abilities.INTIMIDATE, Abilities.ANGER_POINT, Abilities.CUD_CHEW, 490, 75, 110, 105, 30, 70, 100, 45, 50, 172, false, null, true), ), new PokemonSpecies(Species.PALDEA_WOOPER, 9, false, false, false, "Water Fish Pokémon", Type.POISON, Type.GROUND, 0.4, 11, Abilities.POISON_POINT, Abilities.WATER_ABSORB, Abilities.UNAWARE, 210, 55, 45, 45, 25, 25, 15, 255, 50, 42, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(Species.BLOODMOON_URSALUNA, 9, true, false, false, "Peat Pokémon", Type.GROUND, Type.NORMAL, 2.7, 333, Abilities.MINDS_EYE, Abilities.NONE, Abilities.NONE, 555, 113, 70, 120, 135, 65, 52, 75, 50, 275, GrowthRate.MEDIUM_FAST, 50, false), //Marked as Sub-Legend, for casing purposes + new PokemonSpecies(Species.BLOODMOON_URSALUNA, 9, true, false, false, "Peat Pokémon", Type.GROUND, Type.NORMAL, 2.7, 333, Abilities.MINDS_EYE, Abilities.NONE, Abilities.NONE, 555, 113, 70, 120, 135, 65, 52, 75, 50, 278, GrowthRate.MEDIUM_FAST, 50, false), //Marked as Sub-Legend, for casing purposes ); } diff --git a/src/field/arena.ts b/src/field/arena.ts index acdf6171474..67b83e9518f 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -766,6 +766,8 @@ export class Arena { return 0.000; case Biome.SNOWY_FOREST: return 3.047; + case Biome.END: + return 17.153; default: console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`); return 0; diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 023f907a30d..60a0513f608 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -5,7 +5,7 @@ import { SceneBase } from "#app/scene-base"; import { WindowVariant, getWindowVariantSuffix } from "#app/ui/ui-theme"; import { isMobile } from "#app/touch-controls"; import * as Utils from "#app/utils"; -import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; +import { initPokemonPrevolutions, initPokemonStarters } from "#app/data/balance/pokemon-evolutions"; import { initBiomes } from "#app/data/balance/biomes"; import { initEggMoves } from "#app/data/balance/egg-moves"; import { initPokemonForms } from "#app/data/pokemon-forms"; @@ -103,6 +103,8 @@ export class LoadingScene extends SceneBase { this.loadImage("icon_tera", "ui"); this.loadImage("type_tera", "ui"); this.loadAtlas("type_bgs", "ui"); + this.loadImage("mystery_egg", "ui"); + this.loadImage("normal_memory", "ui"); this.loadImage("dawn_icon_fg", "ui"); this.loadImage("dawn_icon_mg", "ui"); @@ -154,6 +156,7 @@ export class LoadingScene extends SceneBase { this.loadImage("scroll_bar_handle", "ui"); this.loadImage("starter_container_bg", "ui"); this.loadImage("starter_select_bg", "ui"); + this.loadImage("pokedex_summary_bg", "ui"); this.loadImage("select_cursor", "ui"); this.loadImage("select_cursor_highlight", "ui"); this.loadImage("select_cursor_highlight_thick", "ui"); @@ -354,6 +357,7 @@ export class LoadingScene extends SceneBase { initVouchers(); initStatsKeys(); initPokemonPrevolutions(); + initPokemonStarters(); initBiomes(); initEggMoves(); initPokemonForms(); diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 4a0c7ead123..414aa84ce6c 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -119,7 +119,8 @@ export class FaintPhase extends PokemonPhase { const alivePlayField = globalScene.getField(true); alivePlayField.forEach(p => applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon)); if (pokemon.turnData?.attacksReceived?.length) { - const defeatSource = globalScene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId); + const defeatSource = this.source; + if (defeatSource?.isOnField()) { applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 4e96214d600..0d486da1998 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -77,30 +77,36 @@ export class TitlePhase extends Phase { this.end(); }; const { gameData } = globalScene; + const options: OptionSelectItem[] = []; + options.push({ + label: GameMode.getModeName(GameModes.CLASSIC), + handler: () => { + setModeAndEnd(GameModes.CLASSIC); + return true; + } + }); + options.push({ + label: i18next.t("menu:dailyRun"), + handler: () => { + this.initDailyRun(); + return true; + } + }); if (gameData.isUnlocked(Unlockables.ENDLESS_MODE)) { - const options: OptionSelectItem[] = [ - { - label: GameMode.getModeName(GameModes.CLASSIC), - handler: () => { - setModeAndEnd(GameModes.CLASSIC); - return true; - } - }, - { - label: GameMode.getModeName(GameModes.CHALLENGE), - handler: () => { - setModeAndEnd(GameModes.CHALLENGE); - return true; - } - }, - { - label: GameMode.getModeName(GameModes.ENDLESS), - handler: () => { - setModeAndEnd(GameModes.ENDLESS); - return true; - } + options.push({ + label: GameMode.getModeName(GameModes.CHALLENGE), + handler: () => { + setModeAndEnd(GameModes.CHALLENGE); + return true; } - ]; + }); + options.push({ + label: GameMode.getModeName(GameModes.ENDLESS), + handler: () => { + setModeAndEnd(GameModes.ENDLESS); + return true; + } + }); if (gameData.isUnlocked(Unlockables.SPLICED_ENDLESS_MODE)) { options.push({ label: GameMode.getModeName(GameModes.SPLICED_ENDLESS), @@ -110,22 +116,17 @@ export class TitlePhase extends Phase { } }); } - options.push({ - label: i18next.t("menu:cancel"), - handler: () => { - globalScene.clearPhaseQueue(); - globalScene.pushPhase(new TitlePhase()); - super.end(); - return true; - } - }); - globalScene.ui.showText(i18next.t("menu:selectGameMode"), null, () => globalScene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); - } else { - this.gameMode = GameModes.CLASSIC; - globalScene.ui.setMode(Mode.MESSAGE); - globalScene.ui.clearText(); - this.end(); } + options.push({ + label: i18next.t("menu:cancel"), + handler: () => { + globalScene.clearPhaseQueue(); + globalScene.pushPhase(new TitlePhase()); + super.end(); + return true; + } + }); + globalScene.ui.showText(i18next.t("menu:selectGameMode"), null, () => globalScene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); return true; } }, @@ -142,14 +143,6 @@ export class TitlePhase extends Phase { return true; } }, - { - label: i18next.t("menu:dailyRun"), - handler: () => { - this.initDailyRun(); - return true; - }, - keepOpen: true - }, { label: i18next.t("menu:runHistory"), handler: () => { @@ -192,6 +185,7 @@ export class TitlePhase extends Phase { } initDailyRun(): void { + globalScene.ui.clearText(); globalScene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => { globalScene.clearPhaseQueue(); if (slotId === -1) { diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index cc798bc8585..904b51c6dc7 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -193,6 +193,7 @@ export async function initI18n(): Promise { "egg", "fightUiHandler", "filterBar", + "filterText", "gameMode", "gameStatsUiHandler", "growth", @@ -203,6 +204,7 @@ export async function initI18n(): Promise { "move", "nature", "pokeball", + "pokedexUiHandler", "pokemon", "pokemonEvolutions", "pokemonForm", diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index e5662842cd0..b750400d6f5 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -9,6 +9,7 @@ import { EaseType } from "#enums/ease-type"; import { MoneyFormat } from "#enums/money-format"; import { PlayerGender } from "#enums/player-gender"; import { ShopCursorTarget } from "#enums/shop-cursor-target"; +import { isLocal } from "#app/utils"; const VOLUME_OPTIONS: SettingOption[] = new Array(11).fill(null).map((_, i) => i ? { value: (i * 10).toString(), @@ -150,6 +151,7 @@ export const SettingKeys = { Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS", Shop_Cursor_Target: "SHOP_CURSOR_TARGET", Command_Cursor_Memory: "COMMAND_CURSOR_MEMORY", + Dex_For_Devs: "DEX_FOR_DEVS", Candy_Upgrade_Notification: "CANDY_UPGRADE_NOTIFICATION", Candy_Upgrade_Display: "CANDY_UPGRADE_DISPLAY", Move_Info: "MOVE_INFO", @@ -691,6 +693,16 @@ export const Setting: Array = [ } ]; +if (isLocal) { + Setting.push({ + key: SettingKeys.Dex_For_Devs, + label: i18next.t("settings:dexForDevs"), + options: OFF_ON, + default: 0, + type: SettingType.GENERAL + }); +} + /** * Return the index of a Setting * @param key SettingKey @@ -828,6 +840,9 @@ export function setSetting(setting: string, value: number): boolean { case SettingKeys.Command_Cursor_Memory: globalScene.commandCursorMemory = Setting[index].options[value].value === "On"; break; + case SettingKeys.Dex_For_Devs: + globalScene.dexForDevs = Setting[index].options[value].value === "On"; + break; case SettingKeys.EXP_Gains_Speed: globalScene.expGainsSpeed = value; break; diff --git a/src/test/evolution.test.ts b/src/test/evolution.test.ts index 10748899d59..d198049801c 100644 --- a/src/test/evolution.test.ts +++ b/src/test/evolution.test.ts @@ -174,7 +174,7 @@ describe("Evolution", () => { for (let f = 1; f < 4; f++) { vi.spyOn(Utils, "randSeedInt").mockReturnValue(f); // setting the random generator to 1, 2 and 3 to force 4 family mausholds const fourForm = playerPokemon.getEvolution()!; - expect(fourForm.evoFormKey).toBe(null); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is null + expect(fourForm.evoFormKey).toBe("four"); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is "four" } }); }); diff --git a/src/test/moves/fell_stinger.test.ts b/src/test/moves/fell_stinger.test.ts new file mode 100644 index 00000000000..a901ddced44 --- /dev/null +++ b/src/test/moves/fell_stinger.test.ts @@ -0,0 +1,198 @@ +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import GameManager from "../utils/gameManager"; +import { Species } from "#enums/species"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Stat } from "#enums/stat"; +import { StatusEffect } from "#app/enums/status-effect"; +import { WeatherType } from "#app/enums/weather-type"; +import { allMoves } from "#app/data/move"; + + +describe("Moves - Fell Stinger", () => { + 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") + .moveset([ + Moves.FELL_STINGER, + Moves.SALT_CURE, + Moves.BIND, + Moves.LEECH_SEED + ]) + .startingLevel(50) + .disableCrits() + .enemyAbility(Abilities.STURDY) + .enemySpecies(Species.HYPNO) + .enemyMoveset(Moves.SPLASH) + .enemyLevel(5); + }); + + it("should not grant stat boost if opponent gets KO'd by recoil", async () => { + game.override.enemyMoveset([ Moves.DOUBLE_EDGE ]); + + await game.classicMode.startBattle([ Species.LEAVANNY ]); + const leadPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("VictoryPhase"); + + expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should not grant stat boost if enemy is KO'd by status effect", async () => { + game.override + .enemyMoveset(Moves.SPLASH) + .enemyStatusEffect(StatusEffect.BURN); + + await game.classicMode.startBattle([ Species.LEAVANNY ]); + const leadPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("VictoryPhase"); + + expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should not grant stat boost if enemy is KO'd by damaging weather", async () => { + game.override.weather(WeatherType.HAIL); + + await game.classicMode.startBattle([ Species.LEAVANNY ]); + const leadPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("VictoryPhase"); + + expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should not grant stat boost if enemy is KO'd by Dry Skin + Harsh Sunlight", async () => { + game.override + .enemyPassiveAbility(Abilities.STURDY) + .enemyAbility(Abilities.DRY_SKIN) + .weather(WeatherType.HARSH_SUN); + + await game.challengeMode.startBattle([ Species.LEAVANNY ]); + const leadPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("VictoryPhase"); + + expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should not grant stat boost if enemy is saved by Reviver Seed", async () => { + game.override + .enemyAbility(Abilities.BALL_FETCH) + .enemyHeldItems([{ name: "REVIVER_SEED" }]); + + await game.classicMode.startBattle([ Species.LEAVANNY ]); + const leadPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should not grant stat boost if enemy is KO'd by Salt Cure", async () => { + game.override.battleType("double") + .startingLevel(5); + const saltCure = allMoves[Moves.SALT_CURE]; + const fellStinger = allMoves[Moves.FELL_STINGER]; + vi.spyOn(saltCure, "accuracy", "get").mockReturnValue(100); + vi.spyOn(fellStinger, "power", "get").mockReturnValue(50000); + + await game.classicMode.startBattle([ Species.LEAVANNY ]); + const leadPokemon = game.scene.getPlayerPokemon()!; + const leftEnemy = game.scene.getEnemyField()[0]!; + + // Turn 1: set Salt Cure, enemy splashes and does nothing + game.move.select(Moves.SALT_CURE, 0, leftEnemy.getBattlerIndex()); + + // Turn 2: enemy Endures Fell Stinger, then dies to Salt Cure + await game.toNextTurn(); + expect(leftEnemy.isFainted()).toBe(false); + leftEnemy.heal(leftEnemy.getMaxHp()); + game.move.select(Moves.FELL_STINGER); + await game.toNextTurn(); + + expect(leftEnemy.isFainted()).toBe(true); + expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should not grant stat boost if enemy dies to Bind or a similar effect", async () => { + game.override.battleType("double") + .startingLevel(5); + vi.spyOn(allMoves[Moves.BIND], "accuracy", "get").mockReturnValue(100); + vi.spyOn(allMoves[Moves.FELL_STINGER], "power", "get").mockReturnValue(50000); + + await game.classicMode.startBattle([ Species.LEAVANNY ]); + const leadPokemon = game.scene.getPlayerPokemon()!; + const leftEnemy = game.scene.getEnemyField()[0]!; + + // Turn 1: set Bind, enemy splashes and does nothing + game.move.select(Moves.BIND, 0, leftEnemy.getBattlerIndex()); + + // Turn 2: enemy Endures Fell Stinger, then dies to Bind + await game.toNextTurn(); + expect(leftEnemy.isFainted()).toBe(false); + leftEnemy.heal(leftEnemy.getMaxHp()); + game.move.select(Moves.FELL_STINGER, 0, leftEnemy.getBattlerIndex()); + await game.toNextTurn(); + + expect(leftEnemy.isFainted()).toBe(true); + expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should not grant stat boost if enemy dies to Leech Seed", async () => { + game.override.battleType("double") + .startingLevel(5); + vi.spyOn(allMoves[Moves.LEECH_SEED], "accuracy", "get").mockReturnValue(100); + vi.spyOn(allMoves[Moves.FELL_STINGER], "power", "get").mockReturnValue(50000); + + await game.classicMode.startBattle([ Species.LEAVANNY ]); + const leadPokemon = game.scene.getPlayerPokemon()!; + const leftEnemy = game.scene.getEnemyField()[0]!; + + // Turn 1: set Leech Seed, enemy splashes and does nothing + game.move.select(Moves.LEECH_SEED, 0, leftEnemy.getBattlerIndex()); + + // Turn 2: enemy Endures Fell Stinger, then dies to Leech Seed + await game.toNextTurn(); + expect(leftEnemy.isFainted()).toBe(false); + leftEnemy.heal(leftEnemy.getMaxHp()); + game.move.select(Moves.FELL_STINGER, 0, leftEnemy.getBattlerIndex()); + await game.toNextTurn(); + + expect(leftEnemy.isFainted()).toBe(true); + expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should grant stat boost if enemy dies directly to hit", async () => { + game.override.enemyAbility(Abilities.KLUTZ); + + await game.classicMode.startBattle([ Species.LEAVANNY ]); + const leadPokemon = game.scene.getPlayerPokemon(); + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(leadPokemon?.getStatStage(Stat.ATK)).toBe(3); + }); +}); diff --git a/src/test/sprites/pokemonSprite.test.ts b/src/test/sprites/pokemonSprite.test.ts index c29f88d3143..00877bc25c2 100644 --- a/src/test/sprites/pokemonSprite.test.ts +++ b/src/test/sprites/pokemonSprite.test.ts @@ -147,7 +147,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors", errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("check female variant files", () => { @@ -156,7 +156,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors", errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("check back female variant files", () => { @@ -165,7 +165,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors", errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("check back male back variant files", () => { @@ -176,7 +176,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors", errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("check exp back variant files", () => { @@ -185,7 +185,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors", errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("check exp female variant files", () => { @@ -194,7 +194,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors", errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("check exp male variant files", () => { @@ -206,7 +206,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors", errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); // check over every file if it's correctly set in the masterlist @@ -217,7 +217,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors for ", dirPath, errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("look over every file in variant back female and check if present in masterlist", () => { @@ -226,7 +226,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors for ", dirPath, errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("look over every file in variant back male and check if present in masterlist", () => { @@ -236,7 +236,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors for ", dirPath, errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("look over every file in variant exp back and check if present in masterlist", () => { @@ -245,7 +245,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors for ", dirPath, errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("look over every file in variant exp female and check if present in masterlist", () => { @@ -254,7 +254,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors for ", dirPath, errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("look over every file in variant exp male and check if present in masterlist", () => { @@ -263,7 +263,7 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors for ", dirPath, errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); it("look over every file in variant root and check if present in masterlist", () => { @@ -272,6 +272,6 @@ describe("check if every variant's sprite are correctly set", () => { if (errors.length) { console.log("errors for ", dirPath, errors); } - expect(errors.length).toBe(0); + expect(errors).toEqual([]); }); }); diff --git a/src/tutorial.ts b/src/tutorial.ts index b5f688c11fb..6890075a642 100644 --- a/src/tutorial.ts +++ b/src/tutorial.ts @@ -10,6 +10,7 @@ export enum Tutorial { Access_Menu = "ACCESS_MENU", Menu = "MENU", Starter_Select = "STARTER_SELECT", + Pokedex = "POKEDEX", Pokerus = "POKERUS", Stat_Change = "STAT_CHANGE", Select_Item = "SELECT_ITEM", diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 25ad9d87701..e6a0ed7a69c 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -12,6 +12,8 @@ import { globalScene } from "#app/global-scene"; import SettingsDisplayUiHandler from "./ui/settings/settings-display-ui-handler"; import SettingsAudioUiHandler from "./ui/settings/settings-audio-ui-handler"; import RunInfoUiHandler from "./ui/run-info-ui-handler"; +import PokedexUiHandler from "./ui/pokedex-ui-handler"; +import PokedexPageUiHandler from "./ui/pokedex-page-ui-handler"; type ActionKeys = Record void>; @@ -140,7 +142,7 @@ export class UiInputs { } buttonGoToFilter(button: Button): void { - const whitelist = [ StarterSelectUiHandler ]; + const whitelist = [ StarterSelectUiHandler, PokedexUiHandler, PokedexPageUiHandler ]; const uiHandler = globalScene.ui?.getHandler(); if (whitelist.some(handler => uiHandler instanceof handler)) { globalScene.ui.processInput(button); @@ -178,6 +180,7 @@ export class UiInputs { globalScene.ui.setOverlayMode(Mode.MENU); break; case Mode.STARTER_SELECT: + case Mode.POKEDEX_PAGE: this.buttonTouch(); break; case Mode.MENU: @@ -190,7 +193,7 @@ export class UiInputs { } buttonCycleOption(button: Button): void { - const whitelist = [ StarterSelectUiHandler, SettingsUiHandler, RunInfoUiHandler, SettingsDisplayUiHandler, SettingsAudioUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler ]; + const whitelist = [ StarterSelectUiHandler, PokedexUiHandler, PokedexPageUiHandler, SettingsUiHandler, RunInfoUiHandler, SettingsDisplayUiHandler, SettingsAudioUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler ]; const uiHandler = globalScene.ui?.getHandler(); if (whitelist.some(handler => uiHandler instanceof handler)) { globalScene.ui.processInput(button); diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index fb5ce9bc5aa..1840792e667 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -1,11 +1,12 @@ import { globalScene } from "#app/global-scene"; -import { TextStyle, addTextObject, getTextStyleOptions } from "./text"; +import { TextStyle, addBBCodeTextObject, getTextColor, getTextStyleOptions } from "./text"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import { addWindow } from "./ui-theme"; import * as Utils from "../utils"; import { argbFromRgba } from "@material/material-color-utilities"; import { Button } from "#enums/buttons"; +import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; export interface OptionSelectConfig { xOffset?: number; @@ -21,8 +22,10 @@ export interface OptionSelectItem { label: string; handler: () => boolean; onHover?: () => void; + skip?: boolean; keepOpen?: boolean; overrideSound?: boolean; + style?: TextStyle; item?: string; itemArgs?: any[]; } @@ -33,7 +36,7 @@ const scrollDownLabel = "↓"; export default abstract class AbstractOptionSelectUiHandler extends UiHandler { protected optionSelectContainer: Phaser.GameObjects.Container; protected optionSelectBg: Phaser.GameObjects.NineSlice; - protected optionSelectText: Phaser.GameObjects.Text; + protected optionSelectText: BBCodeText; protected optionSelectIcons: Phaser.GameObjects.Sprite[]; protected config: OptionSelectConfig | null; @@ -41,11 +44,17 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { protected blockInput: boolean; protected scrollCursor: number = 0; + protected fullCursor: number = 0; protected scale: number = 0.1666666667; private cursorObj: Phaser.GameObjects.Image | null; + protected unskippedIndices: number[] = []; + + protected defaultTextStyle: TextStyle = TextStyle.WINDOW; + + constructor(mode: Mode | null) { super(mode); } @@ -79,44 +88,54 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { protected setupOptions() { const configOptions = this.config?.options ?? []; - let options: OptionSelectItem[]; + const options: OptionSelectItem[] = configOptions; - // for performance reasons, this limits how many options we can see at once. Without this, it would try to make text options for every single options - // which makes the performance take a hit. If there's not enough options to do this (set to 10 at the moment) and the ui mode !== Mode.AUTO_COMPLETE, - // this is ignored and the original code is untouched, with the options array being all the options from the config - if (configOptions.length >= 10 && globalScene.ui.getMode() === Mode.AUTO_COMPLETE) { - const optionsScrollTotal = configOptions.length; - const optionStartIndex = this.scrollCursor; - const optionEndIndex = Math.min(optionsScrollTotal, optionStartIndex + (!optionStartIndex || this.scrollCursor + (this.config?.maxOptions! - 1) >= optionsScrollTotal ? this.config?.maxOptions! - 1 : this.config?.maxOptions! - 2)); - options = configOptions.slice(optionStartIndex, optionEndIndex + 2); - } else { - options = configOptions; - } + this.unskippedIndices = this.getUnskippedIndices(configOptions); if (this.optionSelectText) { - this.optionSelectText.destroy(); + if (this.optionSelectText instanceof BBCodeText) { + try { + this.optionSelectText.destroy(); + } catch (error) { + console.error("Error while destroying optionSelectText:", error); + } + } else { + console.warn("optionSelectText is not an instance of BBCodeText."); + } } + if (this.optionSelectIcons?.length) { this.optionSelectIcons.map(i => i.destroy()); this.optionSelectIcons.splice(0, this.optionSelectIcons.length); } - this.optionSelectText = addTextObject(0, 0, options.map(o => o.item ? ` ${o.label}` : o.label).join("\n"), TextStyle.WINDOW, { maxLines: options.length }); - this.optionSelectText.setLineSpacing(this.scale * 72); + const optionsWithScroll = (this.config?.options && this.config?.options.length > (this.config?.maxOptions!)) ? this.getOptionsWithScroll() : options; + + // Setting the initial text to establish the width of the select object. We consider all options, even ones that are not displayed, + // Except in the case of autocomplete, where we don't want to set up a text element with potentially hundreds of lines. + const optionsForWidth = globalScene.ui.getMode() === Mode.AUTO_COMPLETE ? optionsWithScroll : options; + this.optionSelectText = addBBCodeTextObject( + 0, 0, optionsForWidth.map(o => o.item + ? `[shadow=${getTextColor(o.style ?? this.defaultTextStyle, true, globalScene.uiTheme)}][color=${getTextColor(o.style ?? TextStyle.WINDOW, false, globalScene.uiTheme)}] ${o.label}[/color][/shadow]` + : `[shadow=${getTextColor(o.style ?? this.defaultTextStyle, true, globalScene.uiTheme)}][color=${getTextColor(o.style ?? TextStyle.WINDOW, false, globalScene.uiTheme)}]${o.label}[/color][/shadow]` + ).join("\n"), + TextStyle.WINDOW, { maxLines: options.length, lineSpacing: 12 } + ); + this.optionSelectText.setOrigin(0, 0); this.optionSelectText.setName("text-option-select"); - this.optionSelectText.setLineSpacing(12); this.optionSelectContainer.add(this.optionSelectText); this.optionSelectContainer.setPosition((globalScene.game.canvas.width / 6) - 1 - (this.config?.xOffset || 0), -48 + (this.config?.yOffset || 0)); - this.optionSelectBg.width = Math.max(this.optionSelectText.displayWidth + 24, this.getWindowWidth()); - - if (this.config?.options && this.config?.options.length > (this.config?.maxOptions!)) { // TODO: is this bang correct? - this.optionSelectText.setText(this.getOptionsWithScroll().map(o => o.label).join("\n")); - } - this.optionSelectBg.height = this.getWindowHeight(); + this.optionSelectText.setPosition(this.optionSelectBg.x - this.optionSelectBg.width + 12 + 24 * this.scale, this.optionSelectBg.y - this.optionSelectBg.height + 2 + 42 * this.scale); - this.optionSelectText.setPositionRelative(this.optionSelectBg, 12 + 24 * this.scale, 2 + 42 * this.scale); + // Now that the container and background widths are established, we can set up the proper text restricted to visible options + this.optionSelectText.setText(optionsWithScroll.map(o => o.item + ? `[shadow=${getTextColor(o.style ?? this.defaultTextStyle, true, globalScene.uiTheme)}][color=${getTextColor(o.style ?? TextStyle.WINDOW, false, globalScene.uiTheme)}] ${o.label}[/color][/shadow]` + : `[shadow=${getTextColor(o.style ?? this.defaultTextStyle, true, globalScene.uiTheme)}][color=${getTextColor(o.style ?? TextStyle.WINDOW, false, globalScene.uiTheme)}]${o.label}[/color][/shadow]` + ).join("\n") + + ); options.forEach((option: OptionSelectItem, i: number) => { if (option.item) { @@ -160,6 +179,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { this.optionSelectContainer.setVisible(true); this.scrollCursor = 0; + this.fullCursor = 0; this.setCursor(0); if (this.config.delay) { @@ -169,6 +189,11 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { globalScene.time.delayedCall(Utils.fixedInt(this.config.delay), () => this.unblockInput()); } + if (this.config?.supportHover) { + // handle hover code if the element supports hover-handlers and the option has the optional hover-handler set. + this.config?.options[this.unskippedIndices[this.fullCursor]]?.onHover?.(); + } + return true; } @@ -177,8 +202,6 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { let success = false; - const options = this.getOptionsWithScroll(); - let playSound = true; if (button === Button.ACTION || button === Button.CANCEL) { @@ -190,15 +213,14 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { success = true; if (button === Button.CANCEL) { if (this.config?.maxOptions && this.config.options.length > this.config.maxOptions) { - this.scrollCursor = (this.config.options.length - this.config.maxOptions) + 1; - this.cursor = options.length - 1; + this.setCursor(this.unskippedIndices.length - 1); } else if (!this.config?.noCancel) { - this.setCursor(options.length - 1); + this.setCursor(this.unskippedIndices.length - 1); } else { return false; } } - const option = this.config?.options[this.cursor + (this.scrollCursor - (this.scrollCursor ? 1 : 0))]; + const option = this.config?.options[this.unskippedIndices[this.fullCursor]]; if (option?.handler()) { if (!option.keepOpen) { this.clear(); @@ -211,7 +233,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { // this is here to differentiate between a Button.SUBMIT vs Button.ACTION within the autocomplete handler // this is here because Button.ACTION is picked up as z on the keyboard, meaning if you're typing and hit z, it'll select the option you've chosen success = true; - const option = this.config?.options[this.cursor + (this.scrollCursor - (this.scrollCursor ? 1 : 0))]; + const option = this.config?.options[this.unskippedIndices[this.fullCursor]]; if (option?.handler()) { if (!option.keepOpen) { this.clear(); @@ -223,15 +245,15 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { } else { switch (button) { case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else if (this.cursor === 0) { - success = this.setCursor(options.length - 1); + if (this.fullCursor === 0) { + success = this.setCursor(this.unskippedIndices.length - 1); + } else if (this.fullCursor) { + success = this.setCursor(this.fullCursor - 1); } break; case Button.DOWN: - if (this.cursor < options.length - 1) { - success = this.setCursor(this.cursor + 1); + if (this.fullCursor < this.unskippedIndices.length - 1) { + success = this.setCursor(this.fullCursor + 1); } else { success = this.setCursor(0); } @@ -239,7 +261,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { } if (this.config?.supportHover) { // handle hover code if the element supports hover-handlers and the option has the optional hover-handler set. - this.config?.options[this.cursor + (this.scrollCursor - (this.scrollCursor ? 1 : 0))]?.onHover?.(); + this.config?.options[this.unskippedIndices[this.fullCursor]]?.onHover?.(); } } @@ -273,7 +295,9 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { const optionsScrollTotal = options.length; const optionStartIndex = this.scrollCursor; - const optionEndIndex = Math.min(optionsScrollTotal, optionStartIndex + (!optionStartIndex || this.scrollCursor + (this.config.maxOptions - 1) >= optionsScrollTotal ? this.config.maxOptions - 1 : this.config.maxOptions - 2)); + const optionEndIndex = Math.min(optionsScrollTotal, optionStartIndex + + (!optionStartIndex || this.scrollCursor + (this.config.maxOptions - 1) >= optionsScrollTotal ? this.config.maxOptions - 1 : this.config.maxOptions - 2) + ); if (this.config?.maxOptions && options.length > this.config.maxOptions) { options.splice(optionEndIndex, optionsScrollTotal); @@ -281,13 +305,15 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { if (optionStartIndex) { options.unshift({ label: scrollUpLabel, - handler: () => true + handler: () => true, + style: this.defaultTextStyle }); } if (optionEndIndex < optionsScrollTotal) { options.push({ label: scrollDownLabel, - handler: () => true + handler: () => true, + style: this.defaultTextStyle }); } } @@ -295,42 +321,64 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { return options; } - setCursor(cursor: number): boolean { - const changed = this.cursor !== cursor; + getUnskippedIndices(options: OptionSelectItem[]) { + const unskippedIndices = options + .map((option, index) => (option.skip ? null : index)) // Map to index or null if skipped + .filter(index => index !== null) as number[]; + return unskippedIndices; + } + + setCursor(fullCursor: number): boolean { + const changed = this.fullCursor !== fullCursor; - let isScroll = false; - const options = this.getOptionsWithScroll(); if (changed && this.config?.maxOptions && this.config.options.length > this.config.maxOptions) { - if (Math.abs(cursor - this.cursor) === options.length - 1) { - // Wrap around the list - const optionsScrollTotal = this.config.options.length; - this.scrollCursor = cursor ? optionsScrollTotal - (this.config.maxOptions - 1) : 0; - this.setupOptions(); + + // If the fullCursor is the last possible value, we go to the bottom + if (fullCursor === this.unskippedIndices.length - 1) { + this.fullCursor = fullCursor; + this.cursor = this.config.maxOptions - (this.config.options.length - this.unskippedIndices[fullCursor]); + this.scrollCursor = this.config.options.length - this.config.maxOptions + 1; + // If the fullCursor is the first possible value, we go to the top + } else if (fullCursor === 0) { + this.fullCursor = fullCursor; + this.cursor = this.unskippedIndices[fullCursor]; + this.scrollCursor = 0; } else { - // Move the cursor up or down by 1 - const isDown = cursor && cursor > this.cursor; + const isDown = fullCursor && fullCursor > this.fullCursor; + if (isDown) { - if (options[cursor].label === scrollDownLabel) { - isScroll = true; - this.scrollCursor++; + // If there are skipped options under the next selection, we show them + const jumpFromCurrent = this.unskippedIndices[fullCursor] - this.unskippedIndices[this.fullCursor]; + const skipsFromNext = this.unskippedIndices[fullCursor + 1] - this.unskippedIndices[fullCursor] - 1; + + if (this.cursor + jumpFromCurrent + skipsFromNext >= this.config.maxOptions - 1) { + this.fullCursor = fullCursor; + this.cursor = this.config.maxOptions - 2 - skipsFromNext; + this.scrollCursor = this.unskippedIndices[this.fullCursor] - this.cursor + 1; + } else { + this.fullCursor = fullCursor; + this.cursor = this.unskippedIndices[fullCursor] - this.scrollCursor + (this.scrollCursor ? 1 : 0); } } else { - if (!cursor && this.scrollCursor) { - isScroll = true; - this.scrollCursor--; + const jumpFromPrevious = this.unskippedIndices[fullCursor] - this.unskippedIndices[fullCursor - 1]; + + if (this.cursor - jumpFromPrevious < 1) { + this.fullCursor = fullCursor; + this.cursor = 1; + this.scrollCursor = this.unskippedIndices[this.fullCursor] - this.cursor + 1; + } else { + this.fullCursor = fullCursor; + this.cursor = this.unskippedIndices[fullCursor] - this.scrollCursor + (this.scrollCursor ? 1 : 0); } } - if (isScroll && this.scrollCursor === 1) { - this.scrollCursor += isDown ? 1 : -1; - } } - } - if (isScroll) { - this.setupOptions(); } else { - this.cursor = cursor; + this.fullCursor = fullCursor; + this.cursor = this.unskippedIndices[fullCursor]; } + this.setupOptions(); + if (!this.cursorObj) { this.cursorObj = globalScene.add.image(0, 0, "cursor"); this.optionSelectContainer.add(this.cursorObj); @@ -346,6 +394,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { super.clear(); this.config = null; this.optionSelectContainer.setVisible(false); + this.fullCursor = 0; this.scrollCursor = 0; this.eraseCursor(); } diff --git a/src/ui/base-stats-overlay.ts b/src/ui/base-stats-overlay.ts new file mode 100644 index 00000000000..f2e94fa24a4 --- /dev/null +++ b/src/ui/base-stats-overlay.ts @@ -0,0 +1,121 @@ +import type { InfoToggle } from "../battle-scene"; +import { TextStyle, addTextObject } from "./text"; +import { addWindow } from "./ui-theme"; +import * as Utils from "../utils"; +import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; + +interface BaseStatsOverlaySettings { + scale?:number; // scale the box? A scale of 0.5 is recommended + x?: number; + y?: number; + /** Default is always half the screen, regardless of scale */ + width?: number; +} + +const HEIGHT = 120; +const BORDER = 8; +const GLOBAL_SCALE = 6; +const shortStats = [ "HP", "ATK", "DEF", "SPATK", "SPDEF", "SPD" ]; + +export class BaseStatsOverlay extends Phaser.GameObjects.Container implements InfoToggle { + + public active: boolean = false; + + private statsLabels: Phaser.GameObjects.Text[] = []; + private statsRectangles: Phaser.GameObjects.Rectangle[] = []; + private statsShadows: Phaser.GameObjects.Rectangle[] = []; + private statsTotalLabel: Phaser.GameObjects.Text; + + private statsBg: Phaser.GameObjects.NineSlice; + + public scale: number; + public width: number; + + constructor(options?: BaseStatsOverlaySettings) { + super(globalScene, options?.x, options?.y); + this.scale = options?.scale || 1; // set up the scale + this.setScale(this.scale); + + // prepare the description box + this.width = (options?.width || BaseStatsOverlay.getWidth(this.scale)) / this.scale; // divide by scale as we always want this to be half a window wide + this.statsBg = addWindow(0, 0, this.width, HEIGHT); + this.statsBg.setOrigin(0, 0); + this.add(this.statsBg); + + for (let i = 0; i < 6; i++) { + const shadow = globalScene.add.rectangle(this.width - BORDER + 1, BORDER + 3 + i * 15, 100, 5, 0x006860); + shadow.setOrigin(1, 0); + this.statsShadows.push(shadow); + this.add(shadow); + + const rectangle = globalScene.add.rectangle(this.width - BORDER, BORDER + 2 + i * 15, 100, 5, 0x66aa99); + rectangle.setOrigin(1, 0); + this.statsRectangles.push(rectangle); + this.add(rectangle); + + const label = addTextObject(BORDER, BORDER - 2 + i * 15, "A", TextStyle.BATTLE_INFO); + this.statsLabels.push(label); + this.add(label); + } + + this.statsTotalLabel = addTextObject(BORDER, BORDER + 6 * 15, "A", TextStyle.MONEY_WINDOW); + this.add(this.statsTotalLabel); + + // hide this component for now + this.setVisible(false); + } + + // show this component with infos for the specific move + show(values: number[], total: number):boolean { + + for (let i = 0; i < 6; i++) { + this.statsLabels[i].setText(i18next.t(`pokemonInfo:Stat.${shortStats[i]}shortened`) + ": " + `${values[i]}`); + // This accounts for base stats up to 200, might not be enough. + // TODO: change color based on value. + this.statsShadows[i].setSize(values[i] / 2, 5); + this.statsRectangles[i].setSize(values[i] / 2, 5); + } + + this.statsTotalLabel.setText(i18next.t("pokedexUiHandler:baseTotal") + ": " + `${total}`); + + + this.setVisible(true); + this.active = true; + return true; + } + + clear() { + this.setVisible(false); + this.active = false; + } + + toggleInfo(visible: boolean): void { + if (visible) { + this.setVisible(true); + } + globalScene.tweens.add({ + targets: this.statsLabels, + duration: Utils.fixedInt(125), + ease: "Sine.easeInOut", + alpha: visible ? 1 : 0 + }); + if (!visible) { + this.setVisible(false); + } + } + + isActive(): boolean { + return this.active; + } + + // width of this element + static getWidth(scale:number):number { + return globalScene.game.canvas.width / GLOBAL_SCALE / 2; + } + + // height of this element + static getHeight(scale:number, onSide?: boolean):number { + return HEIGHT * scale; + } +} diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts index 67599b843d8..718058c7f99 100644 --- a/src/ui/dropdown.ts +++ b/src/ui/dropdown.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; import { addTextObject, TextStyle } from "./text"; import { addWindow, WindowVariant } from "./ui-theme"; +import { ScrollBar } from "#app/ui/scroll-bar"; import i18next from "i18next"; export enum DropDownState { @@ -293,21 +294,37 @@ export class DropDown extends Phaser.GameObjects.Container { private onChange: () => void; private lastDir: SortDirection = SortDirection.ASC; private defaultSettings: any[]; + private dropDownScrollBar: ScrollBar; + private totalOptions: number = 0; + private maxOptions: number = 0; + private shownOptions: number = 0; + private tooManyOptions: Boolean = false; + private firstShown: number = 0; + private optionHeight: number = 0; + private optionSpacing: number = 0; + private optionPaddingX: number = 4; + private optionPaddingY: number = 6; + private optionWidth: number = 100; + private cursorOffset: number = 0; constructor(x: number, y: number, options: DropDownOption[], onChange: () => void, type: DropDownType = DropDownType.MULTI, optionSpacing: number = 2) { const windowPadding = 5; - const optionHeight = 7; - const optionPaddingX = 4; - const optionPaddingY = 6; const cursorOffset = 7; - const optionWidth = 100; super(globalScene, x - cursorOffset - windowPadding, y); + + this.optionWidth = 100; + this.optionHeight = 7; + this.optionSpacing = optionSpacing; + this.optionPaddingX = 4; + this.optionPaddingY = 6; + this.cursorOffset = cursorOffset; + this.options = options; this.dropDownType = type; this.onChange = onChange; - this.cursorObj = globalScene.add.image(optionPaddingX + 3, 0, "cursor"); + this.cursorObj = globalScene.add.image(this.optionPaddingX + 3, 0, "cursor"); this.cursorObj.setScale(0.5); this.cursorObj.setOrigin(0, 0.5); this.cursorObj.setVisible(false); @@ -317,31 +334,51 @@ export class DropDown extends Phaser.GameObjects.Container { this.options.unshift(new DropDownOption("ALL", new DropDownLabel(i18next.t("filterBar:all"), undefined, this.checkForAllOn() ? DropDownState.ON : DropDownState.OFF))); } + this.maxOptions = 19; + this.totalOptions = this.options.length; + this.tooManyOptions = this.totalOptions > this.maxOptions; + this.shownOptions = this.tooManyOptions ? this.maxOptions : this.totalOptions; + this.defaultSettings = this.getSettings(); // Place ui elements in the correct spot options.forEach((option, index) => { + const toggleVisibility = type !== DropDownType.SINGLE || option.state === DropDownState.ON; option.setupToggleIcon(type, toggleVisibility); - option.width = optionWidth; - option.y = index * optionHeight + index * optionSpacing + optionPaddingY; + option.width = this.optionWidth; + option.y = index * this.optionHeight + index * optionSpacing + this.optionPaddingY; - const baseX = cursorOffset + optionPaddingX + 3; - const baseY = optionHeight / 2; + const baseX = cursorOffset + this.optionPaddingX + 3; + const baseY = this.optionHeight / 2; option.setLabelPosition(baseX + 8, baseY); if (type === DropDownType.SINGLE) { option.setTogglePosition(baseX + 3, baseY + 1); } else { option.setTogglePosition(baseX, baseY); } + + if (index >= this.shownOptions) { + option.visible = false; + } + + this.firstShown = 0; }); - this.window = addWindow(0, 0, optionWidth, options[options.length - 1].y + optionHeight + optionPaddingY, false, false, undefined, undefined, WindowVariant.XTHIN); + this.window = addWindow(0, 0, this.optionWidth, options[this.shownOptions - 1].y + this.optionHeight + this.optionPaddingY, false, false, undefined, undefined, WindowVariant.XTHIN); this.add(this.window); this.add(options); this.add(this.cursorObj); this.setVisible(false); + + if (this.tooManyOptions) { + // Setting the last parameter to 1 turns out to be optimal in all cases. + this.dropDownScrollBar = new ScrollBar(this.window.width - 3, 5, 5, this.window.height - 10, 1); + this.add(this.dropDownScrollBar); + this.dropDownScrollBar.setTotalRows(this.totalOptions); + this.dropDownScrollBar.setScrollCursor(0); + } } getWidth(): number { @@ -371,6 +408,11 @@ export class DropDown extends Phaser.GameObjects.Container { } setCursor(cursor: number): boolean { + + if (this.tooManyOptions) { + this.setLabels(cursor); + } + this.cursor = cursor; if (cursor < 0) { cursor = 0; @@ -393,6 +435,41 @@ export class DropDown extends Phaser.GameObjects.Container { return true; } + setLabels(cursor: number) { + + if ((cursor === 0) && (this.lastCursor === this.totalOptions - 1)) { + this.firstShown = 0; + } else if ((cursor === this.totalOptions - 1) && (this.lastCursor === 0)) { + this.firstShown = this.totalOptions - this.shownOptions; + } else if ((cursor - this.firstShown >= this.shownOptions) && (cursor > this.lastCursor)) { + this.firstShown += 1; + } else if ((cursor < this.firstShown) && (cursor < this.lastCursor)) { + this.firstShown -= 1; + } + + this.options.forEach((option, index) => { + + option.y = (index - this.firstShown) * (this.optionHeight + this.optionSpacing) + this.optionPaddingY; + + const baseX = this.cursorOffset + this.optionPaddingX + 3; + const baseY = this.optionHeight / 2; + option.setLabelPosition(baseX + 8, baseY); + if (this.dropDownType === DropDownType.SINGLE) { + option.setTogglePosition(baseX + 3, baseY + 1); + } else { + option.setTogglePosition(baseX, baseY); + } + + if ((index < this.firstShown) || ( index >= this.firstShown + this.shownOptions)) { + option.visible = false; + } else { + option.visible = true; + } + }); + + this.dropDownScrollBar.setScrollCursor(cursor); + } + /** * Switch the option at the provided index to its next state and update visuals * Update accordingly the other options if needed: @@ -597,7 +674,12 @@ export class DropDown extends Phaser.GameObjects.Container { x = this.options[i].getCurrentLabelX() ?? 0; } } - this.window.width = maxWidth + x - this.window.x + 6; + this.window.width = maxWidth + x - this.window.x + 9; + + if (this.tooManyOptions) { + this.window.width += 6; + this.dropDownScrollBar.x = this.window.width - 9; + } if (this.x + this.window.width > this.parentContainer.width) { this.x = this.parentContainer.width - this.window.width; diff --git a/src/ui/filter-bar.ts b/src/ui/filter-bar.ts index a6f9f66efe2..1eba81247d4 100644 --- a/src/ui/filter-bar.ts +++ b/src/ui/filter-bar.ts @@ -9,6 +9,7 @@ import { globalScene } from "#app/global-scene"; export enum DropDownColumn { GEN, TYPES, + BIOME, CAUGHT, UNLOCKS, MISC, @@ -25,13 +26,20 @@ export class FilterBar extends Phaser.GameObjects.Container { public openDropDown: boolean = false; private lastCursor: number = -1; private uiTheme: UiTheme; + private leftPaddingX: number; + private rightPaddingX: number; + private cursorOffset: number; - constructor(x: number, y: number, width: number, height: number) { + constructor(x: number, y: number, width: number, height: number, leftPaddingX: number = 6, rightPaddingX: number = 6, cursorOffset: number = 8) { super(globalScene, x, y); this.width = width; this.height = height; + this.leftPaddingX = leftPaddingX; + this.rightPaddingX = rightPaddingX; + this.cursorOffset = cursorOffset; + this.window = addWindow(0, 0, width, height, false, false, undefined, undefined, WindowVariant.THIN); this.add(this.window); @@ -40,8 +48,6 @@ export class FilterBar extends Phaser.GameObjects.Container { this.cursorObj.setVisible(false); this.cursorObj.setOrigin(0, 0); this.add(this.cursorObj); - - this.uiTheme = globalScene.uiTheme; } /** @@ -86,9 +92,9 @@ export class FilterBar extends Phaser.GameObjects.Container { updateFilterLabels(): void { for (let i = 0; i < this.numFilters; i++) { if (this.dropDowns[i].hasDefaultValues()) { - this.labels[i].setColor(getTextColor(TextStyle.TOOLTIP_CONTENT, false, this.uiTheme)); + this.labels[i].setColor(getTextColor(TextStyle.TOOLTIP_CONTENT, false, globalScene.uiTheme)); } else { - this.labels[i].setColor(getTextColor(TextStyle.STATS_LABEL, false, this.uiTheme)); + this.labels[i].setColor(getTextColor(TextStyle.STATS_LABEL, false, globalScene.uiTheme)); } } } @@ -97,23 +103,21 @@ export class FilterBar extends Phaser.GameObjects.Container { * Position the filter dropdowns evenly across the width of the container */ private calcFilterPositions(): void { - const paddingX = 6; - const cursorOffset = 8; - let totalWidth = paddingX * 2 + cursorOffset; + let totalWidth = this.leftPaddingX + this.rightPaddingX + this.cursorOffset; this.labels.forEach(label => { - totalWidth += label.displayWidth + cursorOffset; + totalWidth += label.displayWidth + this.cursorOffset; }); const spacing = (this.width - totalWidth) / (this.labels.length - 1); for (let i = 0; i < this.labels.length; i++) { if (i === 0) { - this.labels[i].x = paddingX + cursorOffset; + this.labels[i].x = this.leftPaddingX + this.cursorOffset; } else { const lastRight = this.labels[i - 1].x + this.labels[i - 1].displayWidth; - this.labels[i].x = lastRight + spacing + cursorOffset; + this.labels[i].x = lastRight + spacing + this.cursorOffset; } - this.dropDowns[i].x = this.labels[i].x - cursorOffset - paddingX; + this.dropDowns[i].x = this.labels[i].x - this.cursorOffset - this.leftPaddingX; this.dropDowns[i].y = this.height; } } @@ -140,8 +144,7 @@ export class FilterBar extends Phaser.GameObjects.Container { } } - const cursorOffset = 8; - this.cursorObj.setPosition(this.labels[cursor].x - cursorOffset + 2, 6); + this.cursorObj.setPosition(this.labels[cursor].x - this.cursorOffset + 2, 6); this.lastCursor = cursor; } diff --git a/src/ui/filter-text.ts b/src/ui/filter-text.ts new file mode 100644 index 00000000000..f961fc9b385 --- /dev/null +++ b/src/ui/filter-text.ts @@ -0,0 +1,218 @@ +import type { StarterContainer } from "./starter-container"; +import { addTextObject, getTextColor, TextStyle } from "./text"; +import type { UiTheme } from "#enums/ui-theme"; +import { addWindow, WindowVariant } from "./ui-theme"; +import i18next from "i18next"; +import type AwaitableUiHandler from "./awaitable-ui-handler"; +import type UI from "./ui"; +import { Mode } from "./ui"; +import { globalScene } from "#app/global-scene"; + +export enum FilterTextRow{ + NAME, + MOVE_1, + MOVE_2, + ABILITY_1, + ABILITY_2, +} + +export class FilterText extends Phaser.GameObjects.Container { + private window: Phaser.GameObjects.NineSlice; + private labels: Phaser.GameObjects.Text[] = []; + private selections: Phaser.GameObjects.Text[] = []; + private selectionStrings: string[] = []; + private rows: FilterTextRow[] = []; + public cursorObj: Phaser.GameObjects.Image; + public numFilters: number = 0; + private lastCursor: number = -1; + private uiTheme: UiTheme; + + private menuMessageBoxContainer: Phaser.GameObjects.Container; + private dialogueMessageBox: Phaser.GameObjects.NineSlice; + message: any; + private readonly textPadding = 8; + private readonly defaultWordWrapWidth = 1224; + + private onChange: () => void; + + public defaultText: string = "---"; + + constructor(x: number, y: number, width: number, height: number, onChange: () => void,) { + super(globalScene, x, y); + + this.onChange = onChange; + + this.width = width; + this.height = height; + + this.window = addWindow(0, 0, width, height, false, false, undefined, undefined, WindowVariant.THIN); + this.add(this.window); + + this.cursorObj = globalScene.add.image(1, 1, "cursor"); + this.cursorObj.setScale(0.5); + this.cursorObj.setVisible(false); + this.cursorObj.setOrigin(0, 0); + this.add(this.cursorObj); + + this.menuMessageBoxContainer = globalScene.add.container(0, 130); + this.menuMessageBoxContainer.setName("menu-message-box"); + this.menuMessageBoxContainer.setVisible(false); + + // Full-width window used for testing dialog messages in debug mode + this.dialogueMessageBox = addWindow(-this.textPadding, 0, globalScene.game.canvas.width / 6 + this.textPadding * 2, 49, false, false, 0, 0, WindowVariant.THIN); + this.dialogueMessageBox.setOrigin(0, 0); + this.menuMessageBoxContainer.add(this.dialogueMessageBox); + + const menuMessageText = addTextObject(this.textPadding, this.textPadding, "", TextStyle.WINDOW, { maxLines: 2 }); + menuMessageText.setName("menu-message"); + menuMessageText.setOrigin(0, 0); + this.menuMessageBoxContainer.add(menuMessageText); + + this.message = menuMessageText; + + } + + /** + * Add a new filter to the FilterBar, as long that a unique DropDownColumn is provided + * @param column the DropDownColumn that will be used to access the filter values + * @param title the string that will get displayed in the filter bar + * @param dropDown the DropDown with all options for this filter + * @returns true if successful, false if the provided column was already in use for another filter + */ + addFilter(row: FilterTextRow, title: string): boolean { + + const paddingX = 6; + const cursorOffset = 8; + const extraSpaceX = 40; + + if (this.rows.includes(row)) { + return false; + } + + this.rows.push(row); + + const filterTypesLabel = addTextObject(paddingX + cursorOffset, 3, title, TextStyle.TOOLTIP_CONTENT); + this.labels.push(filterTypesLabel); + this.add(filterTypesLabel); + + const filterTypesSelection = addTextObject(paddingX + cursorOffset + extraSpaceX, 3, this.defaultText, TextStyle.TOOLTIP_CONTENT); + this.selections.push(filterTypesSelection); + this.add(filterTypesSelection); + + this.selectionStrings.push(""); + + this.calcFilterPositions(); + this.numFilters++; + + return true; + } + + resetSelection(index: number): void { + this.selections[index].setText(this.defaultText); + this.selectionStrings[index] = ""; + this.onChange(); + } + + setValsToDefault(): void { + for (let i = 0; i < this.numFilters; i++) { + this.resetSelection(i); + } + } + + startSearch(index: number, ui: UI): void { + + ui.playSelect(); + const prefilledText = ""; + const buttonAction: any = {}; + buttonAction["buttonActions"] = [ + (sanitizedName: string) => { + ui.playSelect(); + const dialogueTestName = sanitizedName; + //TODO: Is it really necessary to encode and decode? + const dialogueName = decodeURIComponent(escape(atob(dialogueTestName))); + const handler = ui.getHandler() as AwaitableUiHandler; + handler.tutorialActive = true; + // Switch to the dialog test window + this.selections[index].setText(String(i18next.t(dialogueName))); + ui.revertMode(); + this.onChange(); + }, + () => { + ui.revertMode(); + this.onChange; + } + ]; + ui.setOverlayMode(Mode.POKEDEX_SCAN, buttonAction, prefilledText, index); + } + + + setCursor(cursor: number): void { + const cursorOffset = 8; + + this.cursorObj.setPosition(cursorOffset, this.labels[cursor].y + 3); + this.lastCursor = cursor; + } + + /** + * Highlight the labels of the FilterBar if the filters are different from their default values + */ + updateFilterLabels(): void { + for (let i = 0; i < this.numFilters; i++) { + if (this.selections[i].text === this.defaultText) { + this.labels[i].setColor(getTextColor(TextStyle.TOOLTIP_CONTENT, false, globalScene.uiTheme)); + } else { + this.labels[i].setColor(getTextColor(TextStyle.STATS_LABEL, false, globalScene.uiTheme)); + } + } + } + + /** + * Position the filter dropdowns evenly across the width of the container + */ + private calcFilterPositions(): void { + const paddingY = 8; + + let totalHeight = paddingY * 2; + this.labels.forEach(label => { + totalHeight += label.displayHeight; + }); + const spacing = (this.height - totalHeight) / (this.labels.length - 1); + for (let i = 0; i < this.labels.length; i++) { + if (i === 0) { + this.labels[i].y = paddingY; + this.selections[i].y = paddingY; + } else { + const lastBottom = this.labels[i - 1].y + this.labels[i - 1].displayHeight; + this.labels[i].y = lastBottom + spacing; + this.selections[i].y = lastBottom + spacing; + } + } + } + + getValue(row: number): string { + return this.selections[row].getWrappedText()[0]; + } + + /** + * Find the nearest filter to the provided container on the y-axis + * @param container the StarterContainer to compare position against + * @returns the index of the closest filter + */ + getNearestFilter(container: StarterContainer): number { + + const midy = container.y + container.icon.displayHeight / 2; + let nearest = 0; + let nearestDist = 1000; + for (let i = 0; i < this.labels.length; i++) { + const dist = Math.abs(midy - (this.labels[i].y + this.labels[i].displayHeight / 3)); + if (dist < nearestDist) { + nearest = i; + nearestDist = dist; + } + } + + return nearest; + } + + +} diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index aadb39cdcfe..3965eb38cc4 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -23,6 +23,7 @@ enum MenuOptions { STATS, EGG_LIST, EGG_GACHA, + POKEDEX, MANAGE_DATA, COMMUNITY, SAVE_AND_QUIT, @@ -522,6 +523,11 @@ export default class MenuUiHandler extends MessageUiHandler { ui.setOverlayMode(Mode.EGG_GACHA); success = true; break; + case MenuOptions.POKEDEX: + ui.revertMode(); + ui.setOverlayMode(Mode.POKEDEX); + success = true; + break; case MenuOptions.MANAGE_DATA: if (!bypassLogin && !this.manageDataConfig.options.some(o => o.label === i18next.t("menuUiHandler:linkDiscord") || o.label === i18next.t("menuUiHandler:unlinkDiscord"))) { this.manageDataConfig.options.splice(this.manageDataConfig.options.length - 1, 0, diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 3ffd470b6ca..136f098df7e 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -8,7 +8,7 @@ import { Mode } from "#app/ui/ui"; import * as Utils from "#app/utils"; import { PokemonFormChangeItemModifier, PokemonHeldItemModifier, SwitchEffectTransferModifier } from "#app/modifier/modifier"; import { allMoves, ForceSwitchOutAttr } from "#app/data/move"; -import { getGenderColor, getGenderSymbol } from "#app/data/gender"; +import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; import { StatusEffect } from "#enums/status-effect"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; @@ -109,6 +109,7 @@ export enum PartyOption { TEACH, TRANSFER, SUMMARY, + POKEDEX, UNPAUSE_EVOLUTION, SPLICE, UNSPLICE, @@ -218,7 +219,7 @@ export default class PartyUiHandler extends MessageUiHandler { public static NoEffectMessage = i18next.t("partyUiHandler:anyEffect"); - private localizedOptions = [ PartyOption.SEND_OUT, PartyOption.SUMMARY, PartyOption.CANCEL, PartyOption.APPLY, PartyOption.RELEASE, PartyOption.TEACH, PartyOption.SPLICE, PartyOption.UNSPLICE, PartyOption.REVIVE, PartyOption.TRANSFER, PartyOption.UNPAUSE_EVOLUTION, PartyOption.PASS_BATON, PartyOption.RENAME, PartyOption.SELECT ]; + private localizedOptions = [ PartyOption.SEND_OUT, PartyOption.SUMMARY, PartyOption.POKEDEX, PartyOption.CANCEL, PartyOption.APPLY, PartyOption.RELEASE, PartyOption.TEACH, PartyOption.SPLICE, PartyOption.UNSPLICE, PartyOption.REVIVE, PartyOption.TRANSFER, PartyOption.UNPAUSE_EVOLUTION, PartyOption.PASS_BATON, PartyOption.RENAME, PartyOption.SELECT ]; constructor() { super(Mode.PARTY); @@ -397,7 +398,7 @@ export default class PartyUiHandler extends MessageUiHandler { } ui.playSelect(); return true; - } else if ((option !== PartyOption.SUMMARY && option !== PartyOption.UNPAUSE_EVOLUTION && option !== PartyOption.UNSPLICE && option !== PartyOption.RELEASE && option !== PartyOption.CANCEL && option !== PartyOption.RENAME) + } else if ((![ PartyOption.SUMMARY, PartyOption.POKEDEX, PartyOption.UNPAUSE_EVOLUTION, PartyOption.UNSPLICE, PartyOption.RELEASE, PartyOption.CANCEL, PartyOption.RENAME ].includes(option)) || (option === PartyOption.RELEASE && this.partyUiMode === PartyUiMode.RELEASE)) { let filterResult: string | null; const getTransferrableItemsFromPokemon = (pokemon: PlayerPokemon) => @@ -466,6 +467,16 @@ export default class PartyUiHandler extends MessageUiHandler { ui.playSelect(); ui.setModeWithoutClear(Mode.SUMMARY, pokemon).then(() => this.clearOptions()); return true; + } else if (option === PartyOption.POKEDEX) { + ui.playSelect(); + const attributes = { + shiny: pokemon.shiny, + variant: pokemon.variant, + form: pokemon.formIndex, + female: pokemon.gender === Gender.FEMALE ? true : false + }; + ui.setOverlayMode(Mode.POKEDEX_PAGE, pokemon.species, pokemon.formIndex, attributes).then(() => this.clearOptions()); + return true; } else if (option === PartyOption.UNPAUSE_EVOLUTION) { this.clearOptions(); ui.playSelect(); @@ -892,6 +903,7 @@ export default class PartyUiHandler extends MessageUiHandler { } this.options.push(PartyOption.SUMMARY); + this.options.push(PartyOption.POKEDEX); this.options.push(PartyOption.RENAME); if ((pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) || (pokemon.isFusion() && pokemon.fusionSpecies && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId)))) { diff --git a/src/ui/pokedex-info-overlay.ts b/src/ui/pokedex-info-overlay.ts new file mode 100644 index 00000000000..fe0b47b57e0 --- /dev/null +++ b/src/ui/pokedex-info-overlay.ts @@ -0,0 +1,174 @@ +import type { InfoToggle } from "../battle-scene"; +import { TextStyle, addTextObject } from "./text"; +import { addWindow } from "./ui-theme"; +import * as Utils from "../utils"; +import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; + +export interface PokedexInfoOverlaySettings { + delayVisibility?: boolean; // if true, showing the overlay will only set it to active and populate the fields and the handler using this field has to manually call setVisible later. + scale?:number; // scale the box? A scale of 0.5 is recommended + //location and width of the component; unaffected by scaling + x?: number; + y?: number; + /** Default is always half the screen, regardless of scale */ + width?: number; + /** Determines whether to display the small secondary box */ + hideEffectBox?: boolean; + hideBg?: boolean; +} + +const DESC_HEIGHT = 48; +const BORDER = 8; +const GLOBAL_SCALE = 6; + +export default class PokedexInfoOverlay extends Phaser.GameObjects.Container implements InfoToggle { + public active: boolean = false; + + private desc: Phaser.GameObjects.Text; + private descScroll : Phaser.Tweens.Tween | null = null; + + private descBg: Phaser.GameObjects.NineSlice; + + private options: PokedexInfoOverlaySettings; + + private textMaskRect: Phaser.GameObjects.Graphics; + + private maskPointOriginX: number; + private maskPointOriginY: number; + public scale: number; + public width: number; + + constructor(options?: PokedexInfoOverlaySettings) { + super(globalScene, options?.x, options?.y); + this.scale = options?.scale || 1; // set up the scale + this.setScale(this.scale); + this.options = options || {}; + + // prepare the description box + this.width = (options?.width || PokedexInfoOverlay.getWidth(this.scale)) / this.scale; // divide by scale as we always want this to be half a window wide + this.descBg = addWindow(0, 0, this.width, DESC_HEIGHT); + this.descBg.setOrigin(0, 0); + this.add(this.descBg); + + // set up the description; wordWrap uses true pixels, unaffected by any scaling, while other values are affected + this.desc = addTextObject(BORDER, BORDER - 2, "", TextStyle.BATTLE_INFO, { wordWrap: { width: (this.width - (BORDER - 2) * 2) * GLOBAL_SCALE }}); + this.desc.setLineSpacing(i18next.resolvedLanguage === "ja" ? 25 : 5); + + // limit the text rendering, required for scrolling later on + this.maskPointOriginX = options?.x || 0; + this.maskPointOriginY = options?.y || 0; + + if (this.maskPointOriginX < 0) { + this.maskPointOriginX += globalScene.game.canvas.width / GLOBAL_SCALE; + } + if (this.maskPointOriginY < 0) { + this.maskPointOriginY += globalScene.game.canvas.height / GLOBAL_SCALE; + } + + this.textMaskRect = globalScene.make.graphics(); + this.textMaskRect.fillStyle(0xFF0000); + this.textMaskRect.fillRect( + this.maskPointOriginX + BORDER * this.scale, this.maskPointOriginY + (BORDER - 2) * this.scale, + this.width - (BORDER * 2) * this.scale, (DESC_HEIGHT - (BORDER - 2) * 2) * this.scale); + this.textMaskRect.setScale(6); + const textMask = this.createGeometryMask(this.textMaskRect); + + this.add(this.desc); + this.desc.setMask(textMask); + + if (options?.hideBg) { + this.descBg.setVisible(false); + } + + // hide this component for now + this.setVisible(false); + } + + // show this component with infos for the specific move + show(text: string):boolean { + if (!globalScene.enableMoveInfo) { + return false; // move infos have been disabled // TODO:: is `false` correct? i used to be `undeefined` + } + + this.desc.setText(text ?? ""); + + // stop previous scrolling effects and reset y position + if (this.descScroll) { + this.descScroll.remove(); + this.descScroll = null; + this.desc.y = BORDER - 2; + } + + // determine if we need to add new scrolling effects + const lineCount = Math.floor(this.desc.displayHeight * (96 / 72) / 14.83); + + const newHeight = lineCount >= 3 ? 48 : (lineCount === 2 ? 36 : 24); + this.textMaskRect.clear(); + this.textMaskRect.fillStyle(0xFF0000); + this.textMaskRect.fillRect( + this.maskPointOriginX + BORDER * this.scale, + this.maskPointOriginY + (BORDER - 2) * this.scale + (48 - newHeight), + this.width - (BORDER * 2) * this.scale, + (newHeight - (BORDER - 2) * 2) * this.scale + ); + const updatedMask = this.createGeometryMask(this.textMaskRect); + this.desc.setMask(updatedMask); + + this.descBg.setSize(this.descBg.width, newHeight); + this.descBg.setY(48 - newHeight); + this.desc.setY(BORDER - 2 + (48 - newHeight)); + + if (lineCount > 3) { + // generate scrolling effects + this.descScroll = globalScene.tweens.add({ + targets: this.desc, + delay: Utils.fixedInt(2000), + loop: -1, + hold: Utils.fixedInt(2000), + duration: Utils.fixedInt((lineCount - 3) * 2000), + y: `-=${14.83 * (72 / 96) * (lineCount - 3)}` + }); + } + + if (!this.options.delayVisibility) { + this.setVisible(true); + } + this.active = true; + return true; + } + + clear() { + this.setVisible(false); + this.active = false; + } + + toggleInfo(visible: boolean): void { + if (visible) { + this.setVisible(true); + } + globalScene.tweens.add({ + targets: this.desc, + duration: Utils.fixedInt(125), + ease: "Sine.easeInOut", + alpha: visible ? 1 : 0 + }); + if (!visible) { + this.setVisible(false); + } + } + + isActive(): boolean { + return this.active; + } + + // width of this element + static getWidth(scale:number):number { + return globalScene.game.canvas.width / GLOBAL_SCALE / 2; + } + + // height of this element + static getHeight(scale:number, onSide?: boolean):number { + return DESC_HEIGHT * scale; + } +} diff --git a/src/ui/pokedex-mon-container.ts b/src/ui/pokedex-mon-container.ts new file mode 100644 index 00000000000..f3932aa90c8 --- /dev/null +++ b/src/ui/pokedex-mon-container.ts @@ -0,0 +1,164 @@ +import { globalScene } from "#app/global-scene"; +import type PokemonSpecies from "../data/pokemon-species"; +import { addTextObject, TextStyle } from "./text"; + +export class PokedexMonContainer extends Phaser.GameObjects.Container { + public species: PokemonSpecies; + public icon: Phaser.GameObjects.Sprite; + public shinyIcons: Phaser.GameObjects.Image[] = []; + public label: Phaser.GameObjects.Text; + public starterPassiveBgs: Phaser.GameObjects.Image; + public hiddenAbilityIcon: Phaser.GameObjects.Image; + public favoriteIcon: Phaser.GameObjects.Image; + public classicWinIcon: Phaser.GameObjects.Image; + public candyUpgradeIcon: Phaser.GameObjects.Image; + public candyUpgradeOverlayIcon: Phaser.GameObjects.Image; + public eggMove1Icon: Phaser.GameObjects.Image; + public tmMove1Icon: Phaser.GameObjects.Image; + public eggMove2Icon: Phaser.GameObjects.Image; + public tmMove2Icon: Phaser.GameObjects.Image; + public passive1Icon: Phaser.GameObjects.Image; + public passive2Icon: Phaser.GameObjects.Image; + public cost: number = 0; + + constructor(species: PokemonSpecies) { + super(globalScene, 0, 0); + + this.species = species; + + const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, false, true); + const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + + // starter passive bg + const starterPassiveBg = globalScene.add.image(2, 5, "passive_bg"); + starterPassiveBg.setOrigin(0, 0); + starterPassiveBg.setScale(0.75); + starterPassiveBg.setVisible(false); + this.add(starterPassiveBg); + this.starterPassiveBgs = starterPassiveBg; + + // icon + this.icon = globalScene.add.sprite(-2, 2, species.getIconAtlasKey(defaultProps.formIndex, defaultProps.shiny, defaultProps.variant)); + this.icon.setScale(0.5); + this.icon.setOrigin(0, 0); + this.icon.setFrame(species.getIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant)); + this.checkIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant); + this.icon.setTint(0); + this.add(this.icon); + + // shiny icons + for (let i = 0; i < 3; i++) { + const shinyIcon = globalScene.add.image(i * -3 + 12, 2, "shiny_star_small"); + shinyIcon.setScale(0.5); + shinyIcon.setOrigin(0, 0); + shinyIcon.setVisible(false); + this.shinyIcons.push(shinyIcon); + } + this.add(this.shinyIcons); + + // value label + const label = addTextObject(1, 2, "0", TextStyle.WINDOW, { fontSize: "32px" }); + label.setShadowOffset(2, 2); + label.setOrigin(0, 0); + label.setVisible(false); + this.add(label); + this.label = label; + + // hidden ability icon + const abilityIcon = globalScene.add.image(12, 7, "ha_capsule"); + abilityIcon.setOrigin(0, 0); + abilityIcon.setScale(0.5); + abilityIcon.setVisible(false); + this.add(abilityIcon); + this.hiddenAbilityIcon = abilityIcon; + + // favorite icon + const favoriteIcon = globalScene.add.image(0, 7, "favorite"); + favoriteIcon.setOrigin(0, 0); + favoriteIcon.setScale(0.5); + favoriteIcon.setVisible(false); + this.add(favoriteIcon); + this.favoriteIcon = favoriteIcon; + + // classic win icon + const classicWinIcon = globalScene.add.image(0, 12, "champion_ribbon"); + classicWinIcon.setOrigin(0, 0); + classicWinIcon.setScale(0.5); + classicWinIcon.setVisible(false); + this.add(classicWinIcon); + this.classicWinIcon = classicWinIcon; + + // candy upgrade icon + const candyUpgradeIcon = globalScene.add.image(12, 12, "candy"); + candyUpgradeIcon.setOrigin(0, 0); + candyUpgradeIcon.setScale(0.25); + candyUpgradeIcon.setVisible(false); + this.add(candyUpgradeIcon); + this.candyUpgradeIcon = candyUpgradeIcon; + + // candy upgrade overlay icon + const candyUpgradeOverlayIcon = globalScene.add.image(12, 12, "candy_overlay"); + candyUpgradeOverlayIcon.setOrigin(0, 0); + candyUpgradeOverlayIcon.setScale(0.25); + candyUpgradeOverlayIcon.setVisible(false); + this.add(candyUpgradeOverlayIcon); + this.candyUpgradeOverlayIcon = candyUpgradeOverlayIcon; + + // move icons + const eggMove1Icon = globalScene.add.image(0, 12, "mystery_egg"); + eggMove1Icon.setOrigin(0, 0); + eggMove1Icon.setScale(0.25); + eggMove1Icon.setVisible(false); + this.add(eggMove1Icon); + this.eggMove1Icon = eggMove1Icon; + + // move icons + const tmMove1Icon = globalScene.add.image(0, 12, "normal_memory"); + tmMove1Icon.setOrigin(0, 0); + tmMove1Icon.setScale(0.25); + tmMove1Icon.setVisible(false); + this.add(tmMove1Icon); + this.tmMove1Icon = tmMove1Icon; + + // move icons + const eggMove2Icon = globalScene.add.image(7, 12, "mystery_egg"); + eggMove2Icon.setOrigin(0, 0); + eggMove2Icon.setScale(0.25); + eggMove2Icon.setVisible(false); + this.add(eggMove2Icon); + this.eggMove2Icon = eggMove2Icon; + + // move icons + const tmMove2Icon = globalScene.add.image(7, 12, "normal_memory"); + tmMove2Icon.setOrigin(0, 0); + tmMove2Icon.setScale(0.25); + tmMove2Icon.setVisible(false); + this.add(tmMove2Icon); + this.tmMove2Icon = tmMove2Icon; + + + // move icons + const passive1Icon = globalScene.add.image(3, 3, "candy"); + passive1Icon.setOrigin(0, 0); + passive1Icon.setScale(0.25); + passive1Icon.setVisible(false); + this.add(passive1Icon); + this.passive1Icon = passive1Icon; + + // move icons + const passive2Icon = globalScene.add.image(12, 3, "candy"); + passive2Icon.setOrigin(0, 0); + passive2Icon.setScale(0.25); + passive2Icon.setVisible(false); + this.add(passive2Icon); + this.passive2Icon = passive2Icon; + } + + checkIconId(female, formIndex, shiny, variant) { + if (this.icon.frame.name !== this.species.getIconId(female, formIndex, shiny, variant)) { + console.log(`${this.species.name}'s variant icon does not exist. Replacing with default.`); + this.icon.setTexture(this.species.getIconAtlasKey(formIndex, false, variant)); + this.icon.setFrame(this.species.getIconId(female, formIndex, false, variant)); + } + } +} diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts new file mode 100644 index 00000000000..7ab054ea71b --- /dev/null +++ b/src/ui/pokedex-page-ui-handler.ts @@ -0,0 +1,2425 @@ +import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; +import { pokemonEvolutions, pokemonPrevolutions, pokemonStarters } from "#app/data/balance/pokemon-evolutions"; +import type { Variant } from "#app/data/variant"; +import { getVariantTint, getVariantIcon } from "#app/data/variant"; +import { argbFromRgba } from "@material/material-color-utilities"; +import i18next from "i18next"; +import { starterColors } from "#app/battle-scene"; +import { allAbilities } from "#app/data/ability"; +import { speciesEggMoves } from "#app/data/balance/egg-moves"; +import { GrowthRate, getGrowthRateColor } from "#app/data/exp"; +import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; +import { allMoves } from "#app/data/move"; +import { getNatureName } from "#app/data/nature"; +import type { SpeciesFormChange } from "#app/data/pokemon-forms"; +import { pokemonFormChanges } from "#app/data/pokemon-forms"; +import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; +import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; +import type { PokemonForm } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { allSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; +import { starterPassiveAbilities } from "#app/data/balance/passives"; +import { Type } from "#enums/type"; +import { GameModes } from "#app/game-mode"; +import type { DexEntry, StarterAttributes } from "#app/system/game-data"; +import { AbilityAttr, DexAttr } from "#app/system/game-data"; +import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import MessageUiHandler from "#app/ui/message-ui-handler"; +import { StatsContainer } from "#app/ui/stats-container"; +import { TextStyle, addTextObject, getTextStyleOptions } from "#app/ui/text"; +import { Mode } from "#app/ui/ui"; +import { addWindow } from "#app/ui/ui-theme"; +import { Egg } from "#app/data/egg"; +import Overrides from "#app/overrides"; +import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; +import { Passive as PassiveAttr } from "#enums/passive"; +import * as Challenge from "#app/data/challenge"; +import MoveInfoOverlay from "#app/ui/move-info-overlay"; +import PokedexInfoOverlay from "#app/ui/pokedex-info-overlay"; +import { getEggTierForSpecies } from "#app/data/egg"; +import { Device } from "#enums/devices"; +import type { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { Button } from "#enums/buttons"; +import { EggSourceType } from "#enums/egg-source-types"; +import { StarterContainer } from "#app/ui/starter-container"; +import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters"; +import { BooleanHolder, capitalizeString, getLocalizedSpriteKey, isNullOrUndefined, NumberHolder, padInt, rgbHexToRgba, toReadableString } from "#app/utils"; +import type { Nature } from "#enums/nature"; +import BgmBar from "./bgm-bar"; +import * as Utils from "../utils"; +import { speciesTmMoves } from "#app/data/balance/tms"; +import type { BiomeTierTod } from "#app/data/balance/biomes"; +import { BiomePoolTier, catchableSpecies } from "#app/data/balance/biomes"; +import { Biome } from "#app/enums/biome"; +import { TimeOfDay } from "#app/enums/time-of-day"; +import type { Abilities } from "#app/enums/abilities"; +import { BaseStatsOverlay } from "#app/ui/base-stats-overlay"; +import { globalScene } from "#app/global-scene"; + + +interface LanguageSetting { + starterInfoTextSize: string, + instructionTextSize: string, + starterInfoXPos?: number, + starterInfoYOffset?: number +} + +const languageSettings: { [key: string]: LanguageSetting } = { + "en":{ + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, + "de":{ + starterInfoTextSize: "48px", + instructionTextSize: "35px", + starterInfoXPos: 33, + }, + "es-ES":{ + starterInfoTextSize: "56px", + instructionTextSize: "35px", + }, + "fr":{ + starterInfoTextSize: "54px", + instructionTextSize: "38px", + }, + "it":{ + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, + "pt_BR":{ + starterInfoTextSize: "47px", + instructionTextSize: "38px", + starterInfoXPos: 33, + }, + "zh":{ + starterInfoTextSize: "47px", + instructionTextSize: "38px", + starterInfoYOffset: 1, + starterInfoXPos: 24, + }, + "pt":{ + starterInfoTextSize: "48px", + instructionTextSize: "42px", + starterInfoXPos: 33, + }, + "ko":{ + starterInfoTextSize: "52px", + instructionTextSize: "38px", + }, + "ja":{ + starterInfoTextSize: "51px", + instructionTextSize: "38px", + }, + "ca-ES":{ + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, +}; + +const valueReductionMax = 2; + +// Position of UI elements +const speciesContainerX = 109; + +interface SpeciesDetails { + shiny?: boolean, + formIndex?: number + female?: boolean, + variant?: number, + forSeen?: boolean, // default = false +} + +enum MenuOptions { + BASE_STATS, + ABILITIES, + LEVEL_MOVES, + EGG_MOVES, + TM_MOVES, + BIOMES, + NATURES, + TOGGLE_IVS, + EVOLUTIONS +} + + +export default class PokedexPageUiHandler extends MessageUiHandler { + private starterSelectContainer: Phaser.GameObjects.Container; + private shinyOverlay: Phaser.GameObjects.Image; + private starterContainers: StarterContainer[] = []; + private filteredStarterContainers: StarterContainer[] = []; + private pokemonNumberText: Phaser.GameObjects.Text; + private pokemonSprite: Phaser.GameObjects.Sprite; + private pokemonNameText: Phaser.GameObjects.Text; + private pokemonGrowthRateLabelText: Phaser.GameObjects.Text; + private pokemonGrowthRateText: Phaser.GameObjects.Text; + private type1Icon: Phaser.GameObjects.Sprite; + private type2Icon: Phaser.GameObjects.Sprite; + private pokemonLuckLabelText: Phaser.GameObjects.Text; + private pokemonLuckText: Phaser.GameObjects.Text; + private pokemonGenderText: Phaser.GameObjects.Text; + private pokemonUncaughtText: Phaser.GameObjects.Text; + private pokemonCandyContainer: Phaser.GameObjects.Container; + private pokemonCandyIcon: Phaser.GameObjects.Sprite; + private pokemonCandyDarknessOverlay: Phaser.GameObjects.Sprite; + private pokemonCandyOverlayIcon: Phaser.GameObjects.Sprite; + private pokemonCandyCountText: Phaser.GameObjects.Text; + private pokemonCaughtHatchedContainer: Phaser.GameObjects.Container; + private pokemonCaughtCountText: Phaser.GameObjects.Text; + private pokemonFormText: Phaser.GameObjects.Text; + private pokemonHatchedIcon : Phaser.GameObjects.Sprite; + private pokemonHatchedCountText: Phaser.GameObjects.Text; + private pokemonShinyIcon: Phaser.GameObjects.Sprite; + + private activeTooltip: "ABILITY" | "PASSIVE" | "CANDY" | undefined; + private instructionsContainer: Phaser.GameObjects.Container; + private filterInstructionsContainer: Phaser.GameObjects.Container; + private shinyIconElement: Phaser.GameObjects.Sprite; + private formIconElement: Phaser.GameObjects.Sprite; + private genderIconElement: Phaser.GameObjects.Sprite; + private variantIconElement: Phaser.GameObjects.Sprite; + private shinyLabel: Phaser.GameObjects.Text; + private formLabel: Phaser.GameObjects.Text; + private genderLabel: Phaser.GameObjects.Text; + private variantLabel: Phaser.GameObjects.Text; + private candyUpgradeIconElement: Phaser.GameObjects.Sprite; + private candyUpgradeLabel: Phaser.GameObjects.Text; + private showBackSpriteIconElement: Phaser.GameObjects.Sprite; + private showBackSpriteLabel: Phaser.GameObjects.Text; + + private starterSelectMessageBox: Phaser.GameObjects.NineSlice; + private starterSelectMessageBoxContainer: Phaser.GameObjects.Container; + private statsContainer: StatsContainer; + private moveInfoOverlay: MoveInfoOverlay; + private infoOverlay: PokedexInfoOverlay; + private baseStatsOverlay: BaseStatsOverlay; + + private statsMode: boolean; + + private allSpecies: PokemonSpecies[] = []; + private species: PokemonSpecies; + private formIndex: number; + private speciesLoaded: Map = new Map(); + private levelMoves: LevelMoves; + private eggMoves: Moves[] = []; + private hasEggMoves: boolean[] = []; + private tmMoves: Moves[] = []; + private ability1: Abilities; + private ability2: Abilities | undefined; + private abilityHidden: Abilities | undefined; + private passive: Abilities; + private hasPassive: boolean; + private hasAbilities: number[]; + private biomes: BiomeTierTod[]; + private preBiomes: BiomeTierTod[]; + private baseStats: number[]; + private baseTotal: number; + private evolutions: SpeciesFormEvolution[]; + private battleForms: SpeciesFormChange[]; + private prevolutions: SpeciesFormEvolution[]; + + private speciesStarterDexEntry: DexEntry | null; + private canCycleShiny: boolean; + private canCycleForm: boolean; + private canCycleGender: boolean; + + private assetLoadCancelled: BooleanHolder | null; + public cursorObj: Phaser.GameObjects.Image; + + // variables to keep track of the dynamically rendered list of instruction prompts for starter select + private instructionRowX = 0; + private instructionRowY = 0; + private instructionRowTextOffset = 9; + private filterInstructionRowX = 0; + private filterInstructionRowY = 0; + + private starterAttributes: StarterAttributes; + private savedStarterAttributes: StarterAttributes; + + protected blockInput: boolean = false; + protected blockInputOverlay: boolean = false; + + private showBackSprite: boolean = false; + + // Menu + private menuContainer: Phaser.GameObjects.Container; + private menuBg: Phaser.GameObjects.NineSlice; + protected optionSelectText: Phaser.GameObjects.Text; + public bgmBar: BgmBar; + private menuOptions: MenuOptions[]; + protected scale: number = 0.1666666667; + private menuDescriptions: string[]; + + constructor() { + super(Mode.POKEDEX_PAGE); + } + + setup() { + const ui = this.getUi(); + const currentLanguage = i18next.resolvedLanguage ?? "en"; + const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)) ?? "en"; + const textSettings = languageSettings[langSettingKey]; + + this.starterSelectContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); + this.starterSelectContainer.setVisible(false); + ui.add(this.starterSelectContainer); + + const bgColor = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x006860); + bgColor.setOrigin(0, 0); + this.starterSelectContainer.add(bgColor); + + const starterSelectBg = globalScene.add.image(0, 0, "pokedex_summary_bg"); + starterSelectBg.setOrigin(0, 0); + this.starterSelectContainer.add(starterSelectBg); + + this.shinyOverlay = globalScene.add.image(6, 6, "summary_overlay_shiny"); + this.shinyOverlay.setOrigin(0, 0); + this.shinyOverlay.setVisible(false); + this.starterSelectContainer.add(this.shinyOverlay); + + this.pokemonNumberText = addTextObject(17, 1, "0000", TextStyle.SUMMARY); + this.pokemonNumberText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonNumberText); + + this.pokemonNameText = addTextObject(6, 112, "", TextStyle.SUMMARY); + this.pokemonNameText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonNameText); + + this.pokemonGrowthRateLabelText = addTextObject(8, 106, i18next.t("pokedexUiHandler:growthRate"), TextStyle.SUMMARY_ALT, { fontSize: "36px" }); + this.pokemonGrowthRateLabelText.setOrigin(0, 0); + this.pokemonGrowthRateLabelText.setVisible(false); + this.starterSelectContainer.add(this.pokemonGrowthRateLabelText); + + this.pokemonGrowthRateText = addTextObject(34, 106, "", TextStyle.SUMMARY_PINK, { fontSize: "36px" }); + this.pokemonGrowthRateText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonGrowthRateText); + + this.pokemonGenderText = addTextObject(96, 112, "", TextStyle.SUMMARY_ALT); + this.pokemonGenderText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonGenderText); + + this.pokemonUncaughtText = addTextObject(6, 127, i18next.t("pokedexUiHandler:uncaught"), TextStyle.WINDOW, { fontSize: "56px" }); + this.pokemonUncaughtText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonUncaughtText); + + const starterBoxContainer = globalScene.add.container(speciesContainerX + 6, 9); //115 + + for (const species of allSpecies) { + if (!speciesStarterCosts.hasOwnProperty(species.speciesId) || !species.isObtainable()) { + continue; + } + + this.speciesLoaded.set(species.speciesId, false); + this.allSpecies.push(species); + + const starterContainer = new StarterContainer(species).setVisible(false); + this.starterContainers.push(starterContainer); + starterBoxContainer.add(starterContainer); + } + + this.starterSelectContainer.add(starterBoxContainer); + + this.pokemonSprite = globalScene.add.sprite(53, 63, "pkmn__sub"); + this.pokemonSprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); + this.starterSelectContainer.add(this.pokemonSprite); + + this.type1Icon = globalScene.add.sprite(8, 98, getLocalizedSpriteKey("types")); + this.type1Icon.setScale(0.5); + this.type1Icon.setOrigin(0, 0); + this.starterSelectContainer.add(this.type1Icon); + + this.type2Icon = globalScene.add.sprite(26, 98, getLocalizedSpriteKey("types")); + this.type2Icon.setScale(0.5); + this.type2Icon.setOrigin(0, 0); + this.starterSelectContainer.add(this.type2Icon); + + this.pokemonLuckLabelText = addTextObject(8, 89, i18next.t("common:luckIndicator"), TextStyle.WINDOW_ALT, { fontSize: "56px" }); + this.pokemonLuckLabelText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonLuckLabelText); + + this.pokemonLuckText = addTextObject(8 + this.pokemonLuckLabelText.displayWidth + 2, 89, "0", TextStyle.WINDOW, { fontSize: "56px" }); + this.pokemonLuckText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonLuckText); + + // Candy icon and count + this.pokemonCandyContainer = globalScene.add.container(4.5, 18); + + this.pokemonCandyIcon = globalScene.add.sprite(0, 0, "candy"); + this.pokemonCandyIcon.setScale(0.5); + this.pokemonCandyIcon.setOrigin(0, 0); + this.pokemonCandyContainer.add(this.pokemonCandyIcon); + + this.pokemonCandyOverlayIcon = globalScene.add.sprite(0, 0, "candy_overlay"); + this.pokemonCandyOverlayIcon.setScale(0.5); + this.pokemonCandyOverlayIcon.setOrigin(0, 0); + this.pokemonCandyContainer.add(this.pokemonCandyOverlayIcon); + + this.pokemonCandyDarknessOverlay = globalScene.add.sprite(0, 0, "candy"); + this.pokemonCandyDarknessOverlay.setScale(0.5); + this.pokemonCandyDarknessOverlay.setOrigin(0, 0); + this.pokemonCandyDarknessOverlay.setTint(0x000000); + this.pokemonCandyDarknessOverlay.setAlpha(0.50); + this.pokemonCandyContainer.add(this.pokemonCandyDarknessOverlay); + + this.pokemonCandyCountText = addTextObject(9.5, 0, "x0", TextStyle.WINDOW_ALT, { fontSize: "56px" }); + this.pokemonCandyCountText.setOrigin(0, 0); + this.pokemonCandyContainer.add(this.pokemonCandyCountText); + + this.pokemonCandyContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, 30, 20), Phaser.Geom.Rectangle.Contains); + this.starterSelectContainer.add(this.pokemonCandyContainer); + + this.pokemonFormText = addTextObject(6, 42, "Form", TextStyle.WINDOW_ALT, { fontSize: "42px" }); + this.pokemonFormText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonFormText); + + this.pokemonCaughtHatchedContainer = globalScene.add.container(2, 25); + this.pokemonCaughtHatchedContainer.setScale(0.5); + this.starterSelectContainer.add(this.pokemonCaughtHatchedContainer); + + const pokemonCaughtIcon = globalScene.add.sprite(1, 0, "items", "pb"); + pokemonCaughtIcon.setOrigin(0, 0); + pokemonCaughtIcon.setScale(0.75); + this.pokemonCaughtHatchedContainer.add(pokemonCaughtIcon); + + this.pokemonCaughtCountText = addTextObject(24, 4, "0", TextStyle.SUMMARY_ALT); + this.pokemonCaughtCountText.setOrigin(0, 0); + this.pokemonCaughtHatchedContainer.add(this.pokemonCaughtCountText); + + this.pokemonHatchedIcon = globalScene.add.sprite(1, 14, "egg_icons"); + this.pokemonHatchedIcon.setOrigin(0.15, 0.2); + this.pokemonHatchedIcon.setScale(0.8); + this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedIcon); + + this.pokemonShinyIcon = globalScene.add.sprite(14, 76, "shiny_icons"); + this.pokemonShinyIcon.setOrigin(0.15, 0.2); + this.pokemonShinyIcon.setScale(1); + this.pokemonCaughtHatchedContainer.add(this.pokemonShinyIcon); + + this.pokemonHatchedCountText = addTextObject(24, 19, "0", TextStyle.SUMMARY_ALT); + this.pokemonHatchedCountText.setOrigin(0, 0); + this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedCountText); + + // The font size should be set per language + const instructionTextSize = textSettings.instructionTextSize; + + this.instructionsContainer = globalScene.add.container(4, 128); + this.instructionsContainer.setVisible(true); + this.starterSelectContainer.add(this.instructionsContainer); + + this.candyUpgradeIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "C.png"); + this.candyUpgradeIconElement.setName("sprite-candyUpgrade-icon-element"); + this.candyUpgradeIconElement.setScale(0.675); + this.candyUpgradeIconElement.setOrigin(0.0, 0.0); + this.candyUpgradeLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("pokedexUiHandler:candyUpgrade"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.candyUpgradeLabel.setName("text-candyUpgrade-label"); + + // instruction rows that will be pushed into the container dynamically based on need + // creating new sprites since they will be added to the scene later + this.shinyIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "R.png"); + this.shinyIconElement.setName("sprite-shiny-icon-element"); + this.shinyIconElement.setScale(0.675); + this.shinyIconElement.setOrigin(0.0, 0.0); + this.shinyLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("pokedexUiHandler:cycleShiny"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.shinyLabel.setName("text-shiny-label"); + + this.formIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "F.png"); + this.formIconElement.setName("sprite-form-icon-element"); + this.formIconElement.setScale(0.675); + this.formIconElement.setOrigin(0.0, 0.0); + this.formLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("pokedexUiHandler:cycleForm"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.formLabel.setName("text-form-label"); + + this.genderIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "G.png"); + this.genderIconElement.setName("sprite-gender-icon-element"); + this.genderIconElement.setScale(0.675); + this.genderIconElement.setOrigin(0.0, 0.0); + this.genderLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("pokedexUiHandler:cycleGender"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.genderLabel.setName("text-gender-label"); + + this.variantIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "V.png"); + this.variantIconElement.setName("sprite-variant-icon-element"); + this.variantIconElement.setScale(0.675); + this.variantIconElement.setOrigin(0.0, 0.0); + this.variantLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("pokedexUiHandler:cycleVariant"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.variantLabel.setName("text-variant-label"); + + this.showBackSpriteIconElement = new Phaser.GameObjects.Sprite(globalScene, 50, 7, "keyboard", "E.png"); + this.showBackSpriteIconElement.setName("show-backSprite-icon-element"); + this.showBackSpriteIconElement.setScale(0.675); + this.showBackSpriteIconElement.setOrigin(0.0, 0.0); + this.showBackSpriteLabel = addTextObject(60, 7, i18next.t("pokedexUiHandler:showBackSprite"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.showBackSpriteLabel.setName("show-backSprite-label"); + this.starterSelectContainer.add(this.showBackSpriteIconElement); + this.starterSelectContainer.add(this.showBackSpriteLabel); + + this.hideInstructions(); + + this.filterInstructionsContainer = globalScene.add.container(50, 5); + this.filterInstructionsContainer.setVisible(true); + this.starterSelectContainer.add(this.filterInstructionsContainer); + + this.starterSelectMessageBoxContainer = globalScene.add.container(0, globalScene.game.canvas.height / 6); + this.starterSelectMessageBoxContainer.setVisible(false); + this.starterSelectContainer.add(this.starterSelectMessageBoxContainer); + + this.starterSelectMessageBox = addWindow(1, -1, 318, 28); + this.starterSelectMessageBox.setOrigin(0, 1); + this.starterSelectMessageBoxContainer.add(this.starterSelectMessageBox); + + this.message = addTextObject(8, 8, "", TextStyle.WINDOW, { maxLines: 2 }); + this.message.setOrigin(0, 0); + this.starterSelectMessageBoxContainer.add(this.message); + + // arrow icon for the message box + this.initPromptSprite(this.starterSelectMessageBoxContainer); + + this.statsContainer = new StatsContainer(6, 16); + + globalScene.add.existing(this.statsContainer); + + this.statsContainer.setVisible(false); + + this.starterSelectContainer.add(this.statsContainer); + + + // Adding menu container + this.menuContainer = globalScene.add.container(-130, 0); + this.menuContainer.setName("menu"); + this.menuContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + + this.bgmBar = new BgmBar(); + this.bgmBar.setup(); + ui.bgmBar = this.bgmBar; + this.menuContainer.add(this.bgmBar); + this.menuContainer.setVisible(false); + + this.menuOptions = Utils.getEnumKeys(MenuOptions).map(m => parseInt(MenuOptions[m]) as MenuOptions); + + this.optionSelectText = addTextObject(0, 0, this.menuOptions.map(o => `${i18next.t(`pokedexUiHandler:${MenuOptions[o]}`)}`).join("\n"), TextStyle.WINDOW, { maxLines: this.menuOptions.length }); + this.optionSelectText.setLineSpacing(12); + + this.menuDescriptions = [ + i18next.t("pokedexUiHandler:showBaseStats"), + i18next.t("pokedexUiHandler:showAbilities"), + i18next.t("pokedexUiHandler:showLevelMoves"), + i18next.t("pokedexUiHandler:showEggMoves"), + i18next.t("pokedexUiHandler:showTmMoves"), + i18next.t("pokedexUiHandler:showBiomes"), + i18next.t("pokedexUiHandler:showNatures"), + i18next.t("pokedexUiHandler:toggleIVs"), + i18next.t("pokedexUiHandler:showEvolutions") + ]; + + this.scale = getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale; + this.menuBg = addWindow( + (globalScene.game.canvas.width / 6) - (this.optionSelectText.displayWidth + 25), + 0, + this.optionSelectText.displayWidth + 19 + 24 * this.scale, + (globalScene.game.canvas.height / 6) - 2 + ); + this.menuBg.setOrigin(0, 0); + + this.optionSelectText.setPositionRelative(this.menuBg, 10 + 24 * this.scale, 6); + + this.menuContainer.add(this.menuBg); + + this.menuContainer.add(this.optionSelectText); + + ui.add(this.menuContainer); + + this.starterSelectContainer.add(this.menuContainer); + + + // adding base stats + this.baseStatsOverlay = new BaseStatsOverlay({ x: 317, y: 0, width:133 }); + this.menuContainer.add(this.baseStatsOverlay); + this.menuContainer.bringToTop(this.baseStatsOverlay); + + // add the info overlay last to be the top most ui element and prevent the IVs from overlaying this + const overlayScale = 1; + this.moveInfoOverlay = new MoveInfoOverlay({ + scale: overlayScale, + top: true, + x: 1, + y: globalScene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, + }); + this.starterSelectContainer.add(this.moveInfoOverlay); + + this.infoOverlay = new PokedexInfoOverlay({ + scale: overlayScale, + x: 1, + y: globalScene.game.canvas.height / 6 - PokedexInfoOverlay.getHeight(overlayScale) - 29, + }); + this.starterSelectContainer.add(this.infoOverlay); + + // Filter bar sits above everything, except the message box + this.starterSelectContainer.bringToTop(this.starterSelectMessageBoxContainer); + + this.updateInstructions(); + } + + show(args: any[]): boolean { + + if (args.length >= 1 && args[0] === "refresh") { + return false; + } else { + this.species = args[0]; + this.formIndex = args[1] ?? 0; + this.savedStarterAttributes = args[2] ?? { shiny:false, female:true, variant:0, form:0 }; + this.starterSetup(); + } + + this.moveInfoOverlay.clear(); // clear this when removing a menu; the cancel button doesn't seem to trigger this automatically on controllers + this.infoOverlay.clear(); + + super.show(args); + + this.starterSelectContainer.setVisible(true); + this.getUi().bringToTop(this.starterSelectContainer); + + this.starterAttributes = this.initStarterPrefs(); + + this.menuOptions = Utils.getEnumKeys(MenuOptions).map(m => parseInt(MenuOptions[m]) as MenuOptions); + + this.menuContainer.setVisible(true); + + this.speciesStarterDexEntry = this.species ? globalScene.gameData.dexData[this.species.speciesId] : null; + this.setSpecies(); + this.updateInstructions(); + + this.setCursor(0); + + return true; + + } + + starterSetup(): void { + + this.evolutions = []; + this.prevolutions = []; + this.battleForms = []; + + const species = this.species; + const formIndex = this.formIndex ?? 0; + + const allEvolutions = pokemonEvolutions.hasOwnProperty(species.speciesId) ? pokemonEvolutions[species.speciesId] : []; + + if (species.forms.length > 0) { + const form = species.forms[formIndex]; + + // If this form has a specific set of moves, we get them. + this.levelMoves = (formIndex > 0 && pokemonFormLevelMoves.hasOwnProperty(formIndex)) ? pokemonFormLevelMoves[species.speciesId][formIndex] : pokemonSpeciesLevelMoves[species.speciesId]; + this.ability1 = form.ability1; + this.ability2 = (form.ability2 === form.ability1) ? undefined : form.ability2; + this.abilityHidden = (form.abilityHidden === form.ability1) ? undefined : form.abilityHidden; + + this.evolutions = allEvolutions.filter(e => (e.preFormKey === form.formKey || e.preFormKey === null)); + this.baseStats = form.baseStats; + this.baseTotal = form.baseTotal; + + } else { + this.levelMoves = pokemonSpeciesLevelMoves[species.speciesId]; + this.ability1 = species.ability1; + this.ability2 = (species.ability2 === species.ability1) ? undefined : species.ability2; + this.abilityHidden = (species.abilityHidden === species.ability1) ? undefined : species.abilityHidden; + + this.evolutions = allEvolutions; + this.baseStats = species.baseStats; + this.baseTotal = species.baseTotal; + } + + this.eggMoves = speciesEggMoves[this.getStarterSpeciesId(species.speciesId)] ?? []; + this.hasEggMoves = Array.from({ length: 4 }, (_, em) => (globalScene.gameData.starterData[this.getStarterSpeciesId(species.speciesId)].eggMoves & (1 << em)) !== 0); + + const formKey = this.species?.forms.length > 0 ? this.species.forms[this.formIndex].formKey : ""; + this.tmMoves = speciesTmMoves[species.speciesId]?.filter(m => Array.isArray(m) ? (m[0] === formKey ? true : false ) : true) + .map(m => Array.isArray(m) ? m[1] : m).sort((a, b) => allMoves[a].name > allMoves[b].name ? 1 : -1) ?? []; + + const passives = starterPassiveAbilities[this.getStarterSpeciesId(species.speciesId)]; + this.passive = (this.formIndex in passives) ? passives[formIndex] : passives[0]; + + const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(species.speciesId)]; + const abilityAttr = starterData.abilityAttr; + this.hasPassive = starterData.passiveAttr > 0; + + const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1; + const hasAbility2 = abilityAttr & AbilityAttr.ABILITY_2; + const hasHiddenAbility = abilityAttr & AbilityAttr.ABILITY_HIDDEN; + + this.hasAbilities = [ + hasAbility1, + hasAbility2, + hasHiddenAbility + ]; + + const allBiomes = catchableSpecies[species.speciesId] ?? []; + this.preBiomes = this.sanitizeBiomes( + (catchableSpecies[this.getStarterSpeciesId(species.speciesId)] ?? []) + .filter(b => !allBiomes.some(bm => (b.biome === bm.biome && b.tier === bm.tier)) && !(b.biome === Biome.TOWN)), + this.getStarterSpeciesId(species.speciesId)); + this.biomes = this.sanitizeBiomes(allBiomes, species.speciesId); + + const allFormChanges = pokemonFormChanges.hasOwnProperty(species.speciesId) ? pokemonFormChanges[species.speciesId] : []; + this.battleForms = allFormChanges.filter(f => (f.preFormKey === this.species.forms[this.formIndex].formKey)); + + const preSpecies = pokemonPrevolutions.hasOwnProperty(this.species.speciesId) ? allSpecies.find(sp => sp.speciesId === pokemonPrevolutions[this.species.speciesId]) : null; + if (preSpecies) { + const preEvolutions = pokemonEvolutions.hasOwnProperty(preSpecies.speciesId) ? pokemonEvolutions[preSpecies.speciesId] : []; + this.prevolutions = preEvolutions.filter( + e => e.speciesId === species.speciesId && ( + ( + (e.evoFormKey === "" || e.evoFormKey === null) && + ( + // This takes care of Cosplay Pikachu (Pichu is not shown) + (preSpecies.forms.some(form => form.formKey === species.forms[formIndex]?.formKey)) || + // This takes care of Gholdengo + (preSpecies.forms.length > 0 && species.forms.length === 0) || + // This takes care of everything else + (preSpecies.forms.length === 0 && (species.forms.length === 0 || species.forms[formIndex]?.formKey === "")) + ) + ) + // This takes care of Burmy, Shellos etc + || e.evoFormKey === species.forms[formIndex]?.formKey + ) + ); + } + } + + // Function to ensure that forms appear in the appropriate biome and tod + sanitizeBiomes(biomes: BiomeTierTod[], speciesId: number): BiomeTierTod[] { + + if (speciesId === Species.BURMY || speciesId === Species.WORMADAM) { + return biomes.filter(b => { + const formIndex = (() => { + switch (b.biome) { + case Biome.BEACH: + return 1; + case Biome.SLUM: + return 2; + default: + return 0; + } + })(); + return this.formIndex === formIndex; + }); + + } else if (speciesId === Species.ROTOM) { + return biomes.filter(b => { + const formIndex = (() => { + switch (b.biome) { + case Biome.VOLCANO: + return 1; + case Biome.SEA: + return 2; + case Biome.ICE_CAVE: + return 3; + case Biome.MOUNTAIN: + return 4; + case Biome.TALL_GRASS: + return 5; + default: + return 0; + } + })(); + return this.formIndex === formIndex; + }); + + } else if (speciesId === Species.LYCANROC) { + return biomes.filter(b => { + const formIndex = (() => { + switch (b.tod[0]) { + case TimeOfDay.DAY: + case TimeOfDay.DAWN: + return 0; + case TimeOfDay.DUSK: + return 2; + case TimeOfDay.NIGHT: + return 1; + default: + return 0; + } + })(); + return this.formIndex === formIndex; + }); + } + + return biomes; + } + + isCaught(otherSpeciesDexEntry?: DexEntry): bigint { + if (globalScene.dexForDevs) { + return 255n; + } + + const dexEntry = otherSpeciesDexEntry ? otherSpeciesDexEntry : this.speciesStarterDexEntry; + + return dexEntry?.caughtAttr ?? 0n; + } + /** + * Check whether a given form is caught for a given species. + * All forms that can be reached through a form change during battle are considered caught and show up in the dex as such. + * + * @param otherSpecies The species to check; defaults to current species + * @param otherFormIndex The form index of the form to check; defaults to current form + * @returns StarterAttributes for the species + */ + isFormCaught(otherSpecies?: PokemonSpecies, otherFormIndex?: number | undefined): boolean { + + if (globalScene.dexForDevs) { + return true; + } + const species = otherSpecies ? otherSpecies : this.species; + const formIndex = otherFormIndex !== undefined ? otherFormIndex : this.formIndex; + const dexEntry = globalScene.gameData.dexData[species.speciesId]; + + const isFormCaught = dexEntry ? + (dexEntry.caughtAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n + : false; + return isFormCaught; + } + + /** + * Get the starter attributes for the given PokemonSpecies, after sanitizing them. + * If somehow a preference is set for a form, variant, gender, ability or nature + * that wasn't actually unlocked or is invalid it will be cleared here + * + * @param species The species to get Starter Preferences for + * @returns StarterAttributes for the species + */ + initStarterPrefs(): StarterAttributes { + const starterAttributes : StarterAttributes | null = this.species ? { ...this.savedStarterAttributes } : null; + const dexEntry = globalScene.gameData.dexData[this.species.speciesId]; + const caughtAttr = this.isCaught(dexEntry); + + // no preferences or Pokemon wasn't caught, return empty attribute + if (!starterAttributes || !caughtAttr) { + return {}; + } + + const hasShiny = caughtAttr & DexAttr.SHINY; + const hasNonShiny = caughtAttr & DexAttr.NON_SHINY; + if (starterAttributes.shiny && !hasShiny) { + // shiny form wasn't unlocked, purging shiny and variant setting + starterAttributes.shiny = false; + starterAttributes.variant = 0; + } else if (starterAttributes.shiny === false && !hasNonShiny) { + // non shiny form wasn't unlocked, purging shiny setting + starterAttributes.shiny = false; + } + + if (starterAttributes.variant !== undefined) { + const unlockedVariants = [ + hasShiny && caughtAttr & DexAttr.DEFAULT_VARIANT, + hasShiny && caughtAttr & DexAttr.VARIANT_2, + hasShiny && caughtAttr & DexAttr.VARIANT_3 + ]; + if (isNaN(starterAttributes.variant) || starterAttributes.variant < 0) { + starterAttributes.variant = 0; + } else if (!unlockedVariants[starterAttributes.variant]) { + let highestValidIndex = -1; + for (let i = 0; i <= starterAttributes.variant && i < unlockedVariants.length; i++) { + if (unlockedVariants[i] !== 0n) { + highestValidIndex = i; + } + } + // Set to the highest valid index found or default to 0 + starterAttributes.variant = highestValidIndex !== -1 ? highestValidIndex : 0; + } + } + + if (starterAttributes.female !== undefined) { + if ((starterAttributes.female && !(caughtAttr & DexAttr.FEMALE)) || (!starterAttributes.female && !(caughtAttr & DexAttr.MALE))) { + starterAttributes.female = !starterAttributes.female; + } + } + + return starterAttributes; + } + + showText(text: string, delay?: number, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number, moveToTop?: boolean) { + super.showText(text, delay, callback, callbackDelay, prompt, promptDelay); + + const singleLine = text?.indexOf("\n") === -1; + + this.starterSelectMessageBox.setSize(318, singleLine ? 28 : 42); + + if (moveToTop) { + this.starterSelectMessageBox.setOrigin(0, 0); + this.starterSelectMessageBoxContainer.setY(0); + this.message.setY(4); + } else { + this.starterSelectMessageBoxContainer.setY(globalScene.game.canvas.height / 6); + this.starterSelectMessageBox.setOrigin(0, 1); + this.message.setY(singleLine ? -22 : -37); + } + + this.starterSelectMessageBoxContainer.setVisible(!!text?.length); + } + + /** + * Determines if 'Icon' based upgrade notifications should be shown + * @returns true if upgrade notifications are enabled and set to display an 'Icon' + */ + isUpgradeIconEnabled(): boolean { + return globalScene.candyUpgradeNotification !== 0 && globalScene.candyUpgradeDisplay === 0; + } + /** + * Determines if 'Animation' based upgrade notifications should be shown + * @returns true if upgrade notifications are enabled and set to display an 'Animation' + */ + isUpgradeAnimationEnabled(): boolean { + return globalScene.candyUpgradeNotification !== 0 && globalScene.candyUpgradeDisplay === 1; + } + + /** + * If the pokemon is an evolution, find speciesId of its starter. + * @param speciesId the id of the species to check + * @returns the id of the corresponding starter + */ + getStarterSpeciesId(speciesId): number { + if (globalScene.gameData.starterData.hasOwnProperty(speciesId)) { + return speciesId; + } else { + return pokemonStarters[speciesId]; + } + } + + getStarterSpecies(species): PokemonSpecies { + if (globalScene.gameData.starterData.hasOwnProperty(species.speciesId)) { + return species; + } else { + return allSpecies.find(sp => sp.speciesId === pokemonStarters[species.speciesId]) ?? species; + } + } + + /** + * Assign a form string to a given species and form + * @param formKey the form to format + * @param species the species to format + * @param speciesId whether the name of the species should be shown at the end + * @returns the formatted string + */ + getFormString(formKey: string, species: PokemonSpecies, append: boolean = false): string { + let label: string; + const formText = capitalizeString(formKey, "-", false, false) ?? ""; + const speciesName = capitalizeString(this.getStarterSpecies(species).name, "_", true, false) ?? ""; + if (species.speciesId === Species.ARCEUS) { + label = i18next.t(`pokemonInfo:Type.${formText?.toUpperCase()}`); + return label; + } + label = formText ? i18next.t(`pokemonForm:${speciesName}${formText}`) : ""; + if (label === `${speciesName}${formText}`) { + label = i18next.t(`battlePokemonForm:${formKey}`, { pokemonName:species.name }); + } else { + // If the label is only the form, we can append the name of the pokemon + label += append ? ` ${species.name}` : ""; + } + return label; + } + + /** + * Find the name of the region for regional species + * @param species the species to check + * @returns a string with the region name + */ + getRegionName(species: PokemonSpecies): string { + const name = species.name; + const label = Species[species.speciesId]; + const suffix = label.includes("_") ? " (" + label.split("_")[0].toLowerCase() + ")" : ""; + return name + suffix; + } + + processInput(button: Button): boolean { + if (this.blockInput) { + return false; + } + + const ui = this.getUi(); + + let success = false; + let error = false; + + const isCaught = this.isCaught(); + const isFormCaught = this.isFormCaught(); + + if (this.blockInputOverlay) { + if (button === Button.CANCEL || button === Button.ACTION) { + this.blockInputOverlay = false; + this.baseStatsOverlay.clear(); + ui.showText(""); + return true; + } else if (button === Button.UP || button === Button.DOWN) { + this.blockInputOverlay = false; + this.baseStatsOverlay.clear(); + ui.showText(""); + } else { + return false; + } + } + + if (button === Button.SUBMIT) { + success = true; + } else if (button === Button.CANCEL) { + if (this.statsMode) { + this.toggleStatsMode(false); + success = true; + } else { + this.getUi().revertMode(); + success = true; + } + } else { + + const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(this.species.speciesId)]; + // prepare persistent starter data to store changes + const starterAttributes = this.starterAttributes; + + if (button === Button.ACTION) { + + switch (this.cursor) { + + case MenuOptions.BASE_STATS: + + if (!isCaught || !isFormCaught) { + error = true; + } else { + + this.blockInput = true; + + ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => { + ui.showText(i18next.t("pokedexUiHandler:showBaseStats"), null, () => { + + this.baseStatsOverlay.show(this.baseStats, this.baseTotal); + + this.blockInput = false; + this.blockInputOverlay = true; + + return true; + }); + success = true; + }); + } + break; + + case MenuOptions.LEVEL_MOVES: + + if (!isCaught || !isFormCaught) { + error = true; + } else { + + this.blockInput = true; + + ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => { + ui.showText(i18next.t("pokedexUiHandler:showLevelMoves"), null, () => { + + this.moveInfoOverlay.show(allMoves[this.levelMoves[0][1]]); + + ui.setModeWithoutClear(Mode.OPTION_SELECT, { + options: this.levelMoves.map(m => { + const option: OptionSelectItem = { + label: String(m[0]).padEnd(4, " ") + allMoves[m[1]].name, + handler: () => { + return false; + }, + onHover: () => { + this.moveInfoOverlay.show(allMoves[m[1]]); + }, + }; + return option; + }).concat({ + label: i18next.t("menu:cancel"), + handler: () => { + this.moveInfoOverlay.clear(); + this.clearText(); + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + return true; + }, + onHover: () => { + this.moveInfoOverlay.clear(); + }, + }), + supportHover: true, + maxOptions: 8, + yOffset: 19 + }); + + this.blockInput = false; + }); + }); + success = true; + } + break; + + case MenuOptions.EGG_MOVES: + + + if (!isCaught || !isFormCaught) { + error = true; + } else { + + this.blockInput = true; + + ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => { + + if (this.eggMoves.length === 0) { + ui.showText(i18next.t("pokedexUiHandler:noEggMoves")); + this.blockInput = false; + return true; + } + + ui.showText(i18next.t("pokedexUiHandler:showEggMoves"), null, () => { + + this.moveInfoOverlay.show(allMoves[this.eggMoves[0]]); + + ui.setModeWithoutClear(Mode.OPTION_SELECT, { + options: [ + { + label: i18next.t("pokedexUiHandler:common"), + skip: true, + style: TextStyle.MONEY_WINDOW, + handler: () => false, // Non-selectable, but handler is required + onHover: () => this.moveInfoOverlay.clear() // No hover behavior for titles + }, + ...this.eggMoves.slice(0, 3).map((m, i) => ({ + label: allMoves[m].name, + style: this.hasEggMoves[i] ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT, + handler: () => false, + onHover: () => this.moveInfoOverlay.show(allMoves[m]) + })), + { + label: i18next.t("pokedexUiHandler:rare"), + skip: true, + style: TextStyle.MONEY_WINDOW, + handler: () => false, + onHover: () => this.moveInfoOverlay.clear() + }, + { + label: allMoves[this.eggMoves[3]].name, + style: this.hasEggMoves[3] ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT, + handler: () => false, + onHover: () => this.moveInfoOverlay.show(allMoves[this.eggMoves[3]]) + }, + { + label: i18next.t("menu:cancel"), + handler: () => { + this.moveInfoOverlay.clear(); + this.clearText(); + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + return true; + }, + onHover: () => this.moveInfoOverlay.clear() + } + ], + supportHover: true, + maxOptions: 8, + yOffset: 19 + }); + + this.blockInput = false; + }); + }); + success = true; + } + break; + + case MenuOptions.TM_MOVES: + + if (!isCaught || !isFormCaught) { + error = true; + } else { + this.blockInput = true; + + ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => { + ui.showText(i18next.t("pokedexUiHandler:showTmMoves"), null, () => { + + this.moveInfoOverlay.show(allMoves[this.tmMoves[0]]); + + ui.setModeWithoutClear(Mode.OPTION_SELECT, { + options: this.tmMoves.map(m => { + const option: OptionSelectItem = { + label: allMoves[m].name, + handler: () => { + return false; + }, + onHover: () => { + this.moveInfoOverlay.show(allMoves[m]); + }, + }; + return option; + }).concat({ + label: i18next.t("menu:cancel"), + handler: () => { + this.moveInfoOverlay.clear(); + this.clearText(); + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + return true; + }, + onHover: () => { + this.moveInfoOverlay.clear(); + }, + }), + supportHover: true, + maxOptions: 8, + yOffset: 19 + }); + + this.blockInput = false; + }); + }); + success = true; + } + break; + + case MenuOptions.ABILITIES: + + if (!isCaught || !isFormCaught) { + error = true; + } else { + + this.blockInput = true; + + ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => { + + ui.showText(i18next.t("pokedexUiHandler:showAbilities"), null, () => { + + this.infoOverlay.show(allAbilities[this.ability1].description); + + const options: any[] = []; + + if (this.ability1) { + options.push({ + label: allAbilities[this.ability1].name, + style: this.hasAbilities[0] > 0 ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT, + handler: () => false, + onHover: () => this.infoOverlay.show(allAbilities[this.ability1].description) + }); + } + if (this.ability2) { + const ability = allAbilities[this.ability2]; + options.push({ + label: ability?.name, + style: this.hasAbilities[1] > 0 ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT, + handler: () => false, + onHover: () => this.infoOverlay.show(ability?.description) + }); + } + + if (this.abilityHidden) { + options.push({ + label: i18next.t("pokedexUiHandler:hidden"), + skip: true, + style: TextStyle.MONEY_WINDOW, + handler: () => false, + onHover: () => this.infoOverlay.clear() + }); + const ability = allAbilities[this.abilityHidden]; + options.push({ + label: allAbilities[this.abilityHidden].name, + style: this.hasAbilities[2] > 0 ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT, + handler: () => false, + onHover: () => this.infoOverlay.show(ability?.description) + }); + } + + if (this.passive) { + options.push({ + label: i18next.t("pokedexUiHandler:passive"), + skip: true, + style: TextStyle.MONEY_WINDOW, + handler: () => false, + onHover: () => this.infoOverlay.clear() + }); + options.push({ + label: allAbilities[this.passive].name, + style: this.hasPassive ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT, + handler: () => false, + onHover: () => this.infoOverlay.show(allAbilities[this.passive].description) + }); + } + + options.push({ + label: i18next.t("menu:cancel"), + handler: () => { + this.infoOverlay.clear(); + this.clearText(); + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + return true; + }, + onHover: () => this.infoOverlay.clear() + }); + + ui.setModeWithoutClear(Mode.OPTION_SELECT, { + options: options, + supportHover: true, + maxOptions: 8, + yOffset: 19 + }); + + this.blockInput = false; + }); + }); + success = true; + } + break; + + case MenuOptions.BIOMES: + + if (!(this.isCaught() || this.speciesStarterDexEntry?.seenAttr)) { + error = true; + } else { + this.blockInput = true; + + ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => { + + if ((!this.biomes || this.biomes?.length === 0) && + (!this.preBiomes || this.preBiomes?.length === 0)) { + ui.showText(i18next.t("pokedexUiHandler:noBiomes")); + ui.playError(); + this.blockInput = false; + return true; + } + + const options: any[] = []; + + ui.showText(i18next.t("pokedexUiHandler:showBiomes"), null, () => { + + this.biomes.map(b => { + options.push({ + label: i18next.t(`biome:${Biome[b.biome].toUpperCase()}`) + " - " + + i18next.t(`biome:${BiomePoolTier[b.tier].toUpperCase()}`) + + ( b.tod.length === 1 && b.tod[0] === -1 ? "" : " (" + b.tod.map(tod => i18next.t(`biome:${TimeOfDay[tod].toUpperCase()}`)).join(", ") + ")"), + handler: () => false + }); + }); + + + if (this.preBiomes.length > 0) { + options.push({ + label: i18next.t("pokedexUiHandler:preBiomes"), + skip: true, + handler: () => false + }); + this.preBiomes.map(b => { + options.push({ + label: i18next.t(`biome:${Biome[b.biome].toUpperCase()}`) + " - " + + i18next.t(`biome:${BiomePoolTier[b.tier].toUpperCase()}`) + + ( b.tod.length === 1 && b.tod[0] === -1 ? "" : " (" + b.tod.map(tod => i18next.t(`biome:${TimeOfDay[tod].toUpperCase()}`)).join(", ") + ")"), + handler: () => false + }); + }); + } + + options.push({ + label: i18next.t("menu:cancel"), + handler: () => { + this.moveInfoOverlay.clear(); + this.clearText(); + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + return true; + }, + onHover: () => this.moveInfoOverlay.clear() + }); + + ui.setModeWithoutClear(Mode.OPTION_SELECT, { + options: options, + supportHover: true, + maxOptions: 8, + yOffset: 19 + }); + + this.blockInput = false; + }); + }); + success = true; + } + break; + + case MenuOptions.EVOLUTIONS: + + if (!isCaught || !isFormCaught) { + error = true; + } else { + + this.blockInput = true; + + ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => { + + const options: any[] = []; + + if ((!this.prevolutions || this.prevolutions?.length === 0) && + (!this.evolutions || this.evolutions?.length === 0) && + (!this.battleForms || this.battleForms?.length === 0)) { + ui.showText(i18next.t("pokedexUiHandler:noEvolutions")); + ui.playError(); + this.blockInput = false; + return true; + } + + ui.showText(i18next.t("pokedexUiHandler:showEvolutions"), null, () => { + + if (this.prevolutions?.length > 0) { + options.push({ + label: i18next.t("pokedexUiHandler:prevolutions"), + style: TextStyle.MONEY_WINDOW, + skip: true, + handler: () => false + }); + this.prevolutions.map(pre => { + const preSpecies = allSpecies.find(species => species.speciesId === pokemonPrevolutions[this.species.speciesId]); + + const conditionText: string = pre.description; + + options.push({ + label: pre.preFormKey ? + this.getFormString(pre.preFormKey, preSpecies ?? this.species, true) : + this.getRegionName(preSpecies ?? this.species), + handler: () => { + const newSpecies = allSpecies.find(species => species.speciesId === pokemonPrevolutions[pre.speciesId]); + // Attempts to find the formIndex of the prevolved species + const newFormKey = pre.preFormKey ? pre.preFormKey : (this.species.forms.length > 0 ? this.species.forms[this.formIndex].formKey : ""); + const matchingForm = newSpecies?.forms.find(form => form.formKey === newFormKey); + const newFormIndex = matchingForm ? matchingForm.formIndex : 0; + this.starterAttributes.form = newFormIndex; + this.savedStarterAttributes.form = newFormIndex; + this.moveInfoOverlay.clear(); + this.clearText(); + ui.setMode(Mode.POKEDEX_PAGE, newSpecies, newFormIndex, this.savedStarterAttributes); + return true; + }, + onHover: () => this.showText(conditionText) + }); + }); + } + + if (this.evolutions.length > 0) { + options.push({ + label: i18next.t("pokedexUiHandler:evolutions"), + style: TextStyle.MONEY_WINDOW, + skip: true, + handler: () => false + }); + this.evolutions.map(evo => { + const evoSpecies = allSpecies.find(species => species.speciesId === evo.speciesId); + const evoSpeciesStarterDexEntry = evoSpecies ? globalScene.gameData.dexData[evoSpecies.speciesId] : undefined; + const isCaughtEvo = this.isCaught(evoSpeciesStarterDexEntry) ? true : false; + // Attempts to find the formIndex of the evolved species + const newFormKey = evo.evoFormKey ? evo.evoFormKey : (this.species.forms.length > 0 ? this.species.forms[this.formIndex].formKey : ""); + const matchingForm = evoSpecies?.forms.find(form => form.formKey === newFormKey); + const newFormIndex = matchingForm ? matchingForm.formIndex : 0; + const isFormCaughtEvo = this.isFormCaught(evoSpecies, newFormIndex); + + const conditionText: string = evo.description; + + options.push({ + label: evo.evoFormKey ? + this.getFormString(evo.evoFormKey, evoSpecies ?? this.species, true) : + this.getRegionName(evoSpecies ?? this.species), + style: isCaughtEvo && isFormCaughtEvo ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT, + handler: () => { + this.starterAttributes.form = newFormIndex; + this.savedStarterAttributes.form = newFormIndex; + this.moveInfoOverlay.clear(); + this.clearText(); + ui.setMode(Mode.POKEDEX_PAGE, evoSpecies, newFormIndex, this.savedStarterAttributes); + return true; + }, + onHover: () => this.showText(conditionText) + }); + }); + } + + if (this.battleForms.length > 0) { + options.push({ + label: i18next.t("pokedexUiHandler:forms"), + style: TextStyle.MONEY_WINDOW, + skip: true, + handler: () => false + }); + this.battleForms.map(bf => { + + let conditionText:string = ""; + if (bf.trigger) { + conditionText = bf.trigger.description; + } else { + conditionText = ""; + } + let label: string = this.getFormString(bf.formKey, this.species); + if (label === "") { + label = this.species.name; + } + const matchingForm = this.species?.forms.find(form => form.formKey === bf.formKey); + const newFormIndex = matchingForm ? matchingForm.formIndex : 0; + const isFormCaught = this.isFormCaught(this.species, newFormIndex); + + if (conditionText) { + options.push({ + label: label, + style: isFormCaught ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT, + handler: () => { + const newSpecies = this.species; + const newFormIndex = this.species.forms.find(f => f.formKey === bf.formKey)?.formIndex; + this.starterAttributes.form = newFormIndex; + this.savedStarterAttributes.form = newFormIndex; + this.moveInfoOverlay.clear(); + this.clearText(); + ui.setMode(Mode.POKEDEX_PAGE, newSpecies, newFormIndex, this.savedStarterAttributes); + return true; + }, + onHover: () => this.showText(conditionText) + }); + } + }); + } + + options.push({ + label: i18next.t("menu:cancel"), + handler: () => { + this.moveInfoOverlay.clear(); + this.clearText(); + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + return true; + }, + onHover: () => this.moveInfoOverlay.clear() + }); + + ui.setModeWithoutClear(Mode.OPTION_SELECT, { + options: options, + supportHover: true, + maxOptions: 8, + yOffset: 19 + }); + + this.blockInput = false; + }); + }); + success = true; + } + break; + + case MenuOptions.TOGGLE_IVS: + + if (!isCaught || !isFormCaught) { + error = true; + } else { + this.toggleStatsMode(); + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + success = true; + } + break; + + case MenuOptions.NATURES: + + if (!isCaught || !isFormCaught) { + error = true; + } else { + this.blockInput = true; + ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => { + ui.showText(i18next.t("pokedexUiHandler:showNature"), null, () => { + const natures = globalScene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); + ui.setModeWithoutClear(Mode.OPTION_SELECT, { + options: natures.map((n: Nature, i: number) => { + const option: OptionSelectItem = { + label: getNatureName(n, true, true, true, globalScene.uiTheme), + handler: () => { + return false; + } + }; + return option; + }).concat({ + label: i18next.t("menu:cancel"), + handler: () => { + this.clearText(); + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + this.blockInput = false; + return true; + } + }), + maxOptions: 8, + yOffset: 19 + }); + }); + }); + success = true; + } + break; + } + + } else { + const props = globalScene.gameData.getSpeciesDexAttrProps(this.species, this.getCurrentDexProps(this.species.speciesId)); + switch (button) { + case Button.CYCLE_SHINY: + if (this.canCycleShiny) { + + if (!starterAttributes.shiny) { + // Change to shiny, we need to get the proper default variant + const newVariant = starterAttributes.variant ? starterAttributes.variant as Variant : 0; + this.setSpeciesDetails(this.species, { shiny: true, variant: newVariant }); + + globalScene.playSound("se/sparkle"); + // Set the variant label to the shiny tint + const tint = getVariantTint(newVariant); + this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant)); + this.pokemonShinyIcon.setTint(tint); + this.pokemonShinyIcon.setVisible(true); + + starterAttributes.shiny = true; + } else { + let newVariant = props.variant; + do { + newVariant = (newVariant + 1) % 3; + if (newVariant === 0) { + if (this.isCaught() & DexAttr.DEFAULT_VARIANT) { + break; + } + } else if (newVariant === 1) { + if (this.isCaught() & DexAttr.VARIANT_2) { + break; + } + } else { + if (this.isCaught() & DexAttr.VARIANT_3) { + break; + } + } + } while (newVariant !== props.variant); + + starterAttributes.variant = newVariant; // store the selected variant + this.savedStarterAttributes.variant = starterAttributes.variant; + if (newVariant > props.variant) { + this.setSpeciesDetails(this.species, { variant: newVariant as Variant }); + // Cycle tint based on current sprite tint + const tint = getVariantTint(newVariant as Variant); + this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant as Variant)); + this.pokemonShinyIcon.setTint(tint); + success = true; + } else { + this.setSpeciesDetails(this.species, { shiny: false, variant: 0 }); + this.pokemonShinyIcon.setVisible(false); + success = true; + + starterAttributes.shiny = false; + this.savedStarterAttributes.shiny = starterAttributes.shiny; + } + } + } + break; + case Button.CYCLE_FORM: + if (this.canCycleForm) { + const formCount = this.species.forms.length; + let newFormIndex = this.formIndex; + do { + newFormIndex = (newFormIndex + 1) % formCount; + if (this.species.forms[newFormIndex].isStarterSelectable || globalScene.dexForDevs) { // TODO: are those bangs correct? + break; + } + } while (newFormIndex !== props.formIndex); + starterAttributes.form = newFormIndex; // store the selected form + this.savedStarterAttributes.form = starterAttributes.form; + this.formIndex = newFormIndex; + this.starterSetup(); + this.setSpeciesDetails(this.species, { formIndex: newFormIndex }); + success = this.setCursor(this.cursor); + } + break; + case Button.CYCLE_GENDER: + if (this.canCycleGender) { + starterAttributes.female = !props.female; + this.savedStarterAttributes.female = starterAttributes.female; + this.setSpeciesDetails(this.species, { female: !props.female }); + success = true; + } + break; + case Button.STATS: + if (!isCaught || !isFormCaught) { + error = true; + } else { + const ui = this.getUi(); + const options: any[] = []; // TODO: add proper type + + const passiveAttr = starterData.passiveAttr; + const candyCount = starterData.candyCount; + + if (!pokemonPrevolutions.hasOwnProperty(this.species.speciesId)) { + if (!(passiveAttr & PassiveAttr.UNLOCKED)) { + const passiveCost = getPassiveCandyCount(speciesStarterCosts[this.getStarterSpeciesId(this.species.speciesId)]); + options.push({ + label: `x${passiveCost} ${i18next.t("pokedexUiHandler:unlockPassive")} (${allAbilities[this.passive].name})`, + handler: () => { + if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= passiveCost) { + starterData.passiveAttr |= PassiveAttr.UNLOCKED | PassiveAttr.ENABLED; + if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) { + starterData.candyCount -= passiveCost; + } + this.pokemonCandyCountText.setText(`x${starterData.candyCount}`); + globalScene.gameData.saveSystem().then(success => { + if (!success) { + return globalScene.reset(true); + } + }); + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + this.setSpeciesDetails(this.species); + globalScene.playSound("se/buy"); + + return true; + } + return false; + }, + item: "candy", + itemArgs: starterColors[this.getStarterSpeciesId(this.species.speciesId)] + }); + } + + // Reduce cost option + const valueReduction = starterData.valueReduction; + if (valueReduction < valueReductionMax) { + const reductionCost = getValueReductionCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(this.species.speciesId)])[valueReduction]; + options.push({ + label: `x${reductionCost} ${i18next.t("pokedexUiHandler:reduceCost")}`, + handler: () => { + if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= reductionCost) { + starterData.valueReduction++; + if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) { + starterData.candyCount -= reductionCost; + } + this.pokemonCandyCountText.setText(`x${starterData.candyCount}`); + globalScene.gameData.saveSystem().then(success => { + if (!success) { + return globalScene.reset(true); + } + }); + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + globalScene.playSound("se/buy"); + + return true; + } + return false; + }, + item: "candy", + itemArgs: starterColors[this.getStarterSpeciesId(this.species.speciesId)] + }); + } + + // Same species egg menu option. + const sameSpeciesEggCost = getSameSpeciesEggCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(this.species.speciesId)]); + options.push({ + label: `x${sameSpeciesEggCost} ${i18next.t("pokedexUiHandler:sameSpeciesEgg")}`, + handler: () => { + if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost) { + if (globalScene.gameData.eggs.length >= 99 && !Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + // Egg list full, show error message at the top of the screen and abort + this.showText(i18next.t("egg:tooManyEggs"), undefined, () => this.showText("", 0, () => this.tutorialActive = false), 2000, false, undefined, true); + return false; + } + if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) { + starterData.candyCount -= sameSpeciesEggCost; + } + this.pokemonCandyCountText.setText(`x${starterData.candyCount}`); + + const egg = new Egg({ scene: globalScene, species: this.species.speciesId, sourceType: EggSourceType.SAME_SPECIES_EGG }); + egg.addEggToGameData(); + + globalScene.gameData.saveSystem().then(success => { + if (!success) { + return globalScene.reset(true); + } + }); + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + globalScene.playSound("se/buy"); + + return true; + } + return false; + }, + item: "candy", + itemArgs: starterColors[this.getStarterSpeciesId(this.species.speciesId)] + }); + options.push({ + label: i18next.t("menu:cancel"), + handler: () => { + ui.setMode(Mode.POKEDEX_PAGE, "refresh"); + return true; + } + }); + ui.setModeWithoutClear(Mode.OPTION_SELECT, { + options: options, + yOffset: 47 + }); + success = true; + } else { + error = true; + } + } + break; + case Button.CYCLE_ABILITY: + this.showBackSprite = !this.showBackSprite; + if (this.showBackSprite) { + this.showBackSpriteLabel.setText(i18next.t("pokedexUiHandler:showFrontSprite")); + } else { + this.showBackSpriteLabel.setText(i18next.t("pokedexUiHandler:showBackSprite")); + } + this.setSpeciesDetails(this.species, {}, true); + success = true; + break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else { + success = this.setCursor(this.menuOptions.length - 1); + } + break; + case Button.DOWN: + if (this.cursor + 1 < this.menuOptions.length) { + success = this.setCursor(this.cursor + 1); + } else { + success = this.setCursor(0); + } + break; + case Button.LEFT: + this.blockInput = true; + ui.setModeWithoutClear(Mode.OPTION_SELECT).then(() => { + const index = allSpecies.findIndex(species => species.speciesId === this.species.speciesId); + const newIndex = index <= 0 ? allSpecies.length - 1 : index - 1; + const newSpecies = allSpecies[newIndex]; + const matchingForm = newSpecies?.forms.find(form => form.formKey === this.species?.forms[this.formIndex]?.formKey); + const newFormIndex = matchingForm ? matchingForm.formIndex : 0; + this.starterAttributes.form = newFormIndex; + this.savedStarterAttributes.form = newFormIndex; + this.moveInfoOverlay.clear(); + this.clearText(); + ui.setModeForceTransition(Mode.POKEDEX_PAGE, newSpecies, newFormIndex, this.savedStarterAttributes); + }); + this.blockInput = false; + break; + case Button.RIGHT: + ui.setModeWithoutClear(Mode.OPTION_SELECT).then(() => { + const index = allSpecies.findIndex(species => species.speciesId === this.species.speciesId); + const newIndex = index >= allSpecies.length - 1 ? 0 : index + 1; + const newSpecies = allSpecies[newIndex]; + const matchingForm = newSpecies?.forms.find(form => form.formKey === this.species?.forms[this.formIndex]?.formKey); + const newFormIndex = matchingForm ? matchingForm.formIndex : 0; + this.starterAttributes.form = newFormIndex; + this.savedStarterAttributes.form = newFormIndex; + this.moveInfoOverlay.clear(); + this.clearText(); + ui.setModeForceTransition(Mode.POKEDEX_PAGE, newSpecies, newFormIndex, this.savedStarterAttributes); + }); + break; + } + } + } + + if (success) { + ui.playSelect(); + } else if (error) { + ui.playError(); + } + + return success || error; + } + + updateButtonIcon(iconSetting, gamepadType, iconElement, controlLabel): void { + let iconPath; + // touch controls cannot be rebound as is, and are just emulating a keyboard event. + // Additionally, since keyboard controls can be rebound (and will be displayed when they are), we need to have special handling for the touch controls + if (gamepadType === "touch") { + gamepadType = "keyboard"; + switch (iconSetting) { + case SettingKeyboard.Button_Cycle_Shiny: + iconPath = "R.png"; + break; + case SettingKeyboard.Button_Cycle_Form: + iconPath = "F.png"; + break; + case SettingKeyboard.Button_Cycle_Gender: + iconPath = "G.png"; + break; + case SettingKeyboard.Button_Cycle_Ability: + iconPath = "E.png"; + break; + default: + break; + } + } else { + iconPath = globalScene.inputController?.getIconForLatestInputRecorded(iconSetting); + } + iconElement.setTexture(gamepadType, iconPath); + iconElement.setPosition(this.instructionRowX, this.instructionRowY); + controlLabel.setPosition(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY); + iconElement.setVisible(true); + controlLabel.setVisible(true); + this.instructionsContainer.add([ iconElement, controlLabel ]); + this.instructionRowY += 8; + if (this.instructionRowY >= 24) { + this.instructionRowY = 8; + this.instructionRowX += 50; + } + } + + updateInstructions(): void { + this.instructionRowX = 0; + this.instructionRowY = 0; + this.filterInstructionRowX = 0; + this.filterInstructionRowY = 0; + this.hideInstructions(); + this.instructionsContainer.removeAll(); + this.filterInstructionsContainer.removeAll(); + let gamepadType; + if (globalScene.inputMethod === "gamepad") { + gamepadType = globalScene.inputController.getConfig(globalScene.inputController.selectedDevice[Device.GAMEPAD]).padType; + } else { + gamepadType = globalScene.inputMethod; + } + + if (!gamepadType) { + return; + } + + const isFormCaught = this.isFormCaught(); + + if (this.isCaught()) { + if (isFormCaught) { + if (!pokemonPrevolutions.hasOwnProperty(this.species.speciesId)) { + this.updateButtonIcon(SettingKeyboard.Button_Stats, gamepadType, this.candyUpgradeIconElement, this.candyUpgradeLabel); + } + if (this.canCycleShiny) { + this.updateButtonIcon(SettingKeyboard.Button_Cycle_Shiny, gamepadType, this.shinyIconElement, this.shinyLabel); + } + if (this.canCycleGender) { + this.updateButtonIcon(SettingKeyboard.Button_Cycle_Gender, gamepadType, this.genderIconElement, this.genderLabel); + } + } + if (this.canCycleForm) { + this.updateButtonIcon(SettingKeyboard.Button_Cycle_Form, gamepadType, this.formIconElement, this.formLabel); + } + } + } + + getValueLimit(): number { + const valueLimit = new NumberHolder(0); + switch (globalScene.gameMode.modeId) { + case GameModes.ENDLESS: + case GameModes.SPLICED_ENDLESS: + valueLimit.value = 15; + break; + default: + valueLimit.value = 10; + } + + Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_POINTS, valueLimit); + + return valueLimit.value; + } + + + setCursor(cursor: number): boolean { + const ret = super.setCursor(cursor); + + if (!this.cursorObj) { + this.cursorObj = globalScene.add.image(0, 0, "cursor"); + this.cursorObj.setOrigin(0, 0); + this.menuContainer.add(this.cursorObj); + } + + this.cursorObj.setScale(this.scale * 6); + this.cursorObj.setPositionRelative(this.menuBg, 7, 6 + (18 + this.cursor * 96) * this.scale); + + const ui = this.getUi(); + + const isFormCaught = this.isFormCaught(); + + if ((this.isCaught() && isFormCaught) || (this.speciesStarterDexEntry?.seenAttr && cursor === 5)) { + ui.showText(this.menuDescriptions[cursor]); + } else { + ui.showText(""); + } + + return ret; + } + + getFriendship(speciesId: number) { + let currentFriendship = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].friendship; + if (!currentFriendship || currentFriendship === undefined) { + currentFriendship = 0; + } + + const friendshipCap = getStarterValueFriendshipCap(speciesStarterCosts[this.getStarterSpeciesId(speciesId)]); + + return { currentFriendship, friendshipCap }; + } + + setSpecies() { + const species = this.species; + const starterAttributes : StarterAttributes | null = species ? { ...this.starterAttributes } : null; + + if (!species && globalScene.ui.getTooltip().visible) { + globalScene.ui.hideTooltip(); + } + + if (this.statsMode) { + if (this.isCaught()) { + this.statsContainer.setVisible(true); + this.showStats(); + } else { + this.statsContainer.setVisible(false); + //@ts-ignore + this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. what. how? huh? + } + } + + if (species && (this.speciesStarterDexEntry?.seenAttr || this.isCaught())) { + this.pokemonNumberText.setText(padInt(species.speciesId, 4)); + if (starterAttributes?.nickname) { + const name = decodeURIComponent(escape(atob(starterAttributes.nickname))); + this.pokemonNameText.setText(name); + } else { + this.pokemonNameText.setText(species.name); + } + + if (this.isCaught()) { + const colorScheme = starterColors[species.speciesId]; + + const luck = globalScene.gameData.getDexAttrLuck(this.isCaught()); + this.pokemonLuckText.setVisible(!!luck); + this.pokemonLuckText.setText(luck.toString()); + this.pokemonLuckText.setTint(getVariantTint(Math.min(luck - 1, 2) as Variant)); + this.pokemonLuckLabelText.setVisible(this.pokemonLuckText.visible); + + //Growth translate + let growthReadable = toReadableString(GrowthRate[species.growthRate]); + const growthAux = growthReadable.replace(" ", "_"); + if (i18next.exists("growth:" + growthAux)) { + growthReadable = i18next.t("growth:" + growthAux as any); + } + this.pokemonGrowthRateText.setText(growthReadable); + + this.pokemonGrowthRateText.setColor(getGrowthRateColor(species.growthRate)); + this.pokemonGrowthRateText.setShadowColor(getGrowthRateColor(species.growthRate, true)); + this.pokemonGrowthRateLabelText.setVisible(true); + this.pokemonUncaughtText.setVisible(false); + this.pokemonCaughtCountText.setText(`${this.speciesStarterDexEntry?.caughtCount}`); + if (species.speciesId === Species.MANAPHY || species.speciesId === Species.PHIONE) { + this.pokemonHatchedIcon.setFrame("manaphy"); + } else { + this.pokemonHatchedIcon.setFrame(getEggTierForSpecies(species)); + } + this.pokemonHatchedCountText.setText(`${this.speciesStarterDexEntry?.hatchedCount}`); + + const defaultDexAttr = this.getCurrentDexProps(species.speciesId); + const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + const variant = defaultProps.variant; + const tint = getVariantTint(variant); + this.pokemonShinyIcon.setFrame(getVariantIcon(variant)); + this.pokemonShinyIcon.setTint(tint); + this.pokemonShinyIcon.setVisible(defaultProps.shiny); + this.pokemonCaughtHatchedContainer.setVisible(true); + this.pokemonFormText.setVisible(true); + + if (pokemonPrevolutions.hasOwnProperty(species.speciesId)) { + this.pokemonCaughtHatchedContainer.setY(16); + this.pokemonShinyIcon.setY(135); + this.pokemonShinyIcon.setFrame(getVariantIcon(variant)); + [ + this.pokemonCandyContainer, + this.pokemonHatchedIcon, + this.pokemonHatchedCountText + ].map(c => c.setVisible(false)); + this.pokemonFormText.setY(25); + } else { + this.pokemonCaughtHatchedContainer.setY(25); + this.pokemonShinyIcon.setY(117); + this.pokemonCandyIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[0]))); + this.pokemonCandyOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1]))); + this.pokemonCandyCountText.setText(`x${globalScene.gameData.starterData[this.getStarterSpeciesId(species.speciesId)].candyCount}`); + this.pokemonCandyContainer.setVisible(true); + this.pokemonFormText.setY(42); + this.pokemonHatchedIcon.setVisible(true); + this.pokemonHatchedCountText.setVisible(true); + + const { currentFriendship, friendshipCap } = this.getFriendship(this.species.speciesId); + const candyCropY = 16 - (16 * (currentFriendship / friendshipCap)); + this.pokemonCandyDarknessOverlay.setCrop(0, 0, 16, candyCropY); + + this.pokemonCandyContainer.on("pointerover", () => { + globalScene.ui.showTooltip("", `${currentFriendship}/${friendshipCap}`, true); + this.activeTooltip = "CANDY"; + }); + this.pokemonCandyContainer.on("pointerout", () => { + globalScene.ui.hideTooltip(); + this.activeTooltip = undefined; + }); + + } + + // Set default attributes if for some reason starterAttributes does not exist or attributes missing + const props: StarterAttributes = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + if (starterAttributes?.variant && !isNaN(starterAttributes.variant)) { + if (props.shiny) { + props.variant = starterAttributes.variant as Variant; + } + } + props.form = starterAttributes?.form ?? props.form; + props.female = starterAttributes?.female ?? props.female; + + this.setSpeciesDetails(species, { + shiny: props.shiny, + formIndex: props.form, + female: props.female, + variant: props.variant ?? 0, + }); + + if (this.isFormCaught(this.species, props.form)) { + const speciesForm = getPokemonSpeciesForm(species.speciesId, props.form ?? 0); + this.setTypeIcons(speciesForm.type1, speciesForm.type2); + this.pokemonSprite.clearTint(); + } + } else { + this.pokemonGrowthRateText.setText(""); + this.pokemonGrowthRateLabelText.setVisible(false); + this.type1Icon.setVisible(true); + this.type2Icon.setVisible(true); + this.pokemonLuckLabelText.setVisible(false); + this.pokemonLuckText.setVisible(false); + this.pokemonShinyIcon.setVisible(false); + this.pokemonUncaughtText.setVisible(true); + this.pokemonCaughtHatchedContainer.setVisible(true); + this.pokemonCandyContainer.setVisible(false); + this.pokemonFormText.setVisible(false); + + const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, true, true); + const props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + + this.setSpeciesDetails(species, { + shiny: props.shiny, + formIndex: props.formIndex, + female: props.female, + variant: props.variant, + forSeen: true + }); + this.pokemonSprite.setTint(0x808080); + } + } else { + this.pokemonNumberText.setText(species ? padInt(species.speciesId, 4) : ""); + this.pokemonNameText.setText(species ? "???" : ""); + this.pokemonGrowthRateText.setText(""); + this.pokemonGrowthRateLabelText.setVisible(false); + this.type1Icon.setVisible(false); + this.type2Icon.setVisible(false); + this.pokemonLuckLabelText.setVisible(false); + this.pokemonLuckText.setVisible(false); + this.pokemonShinyIcon.setVisible(false); + this.pokemonUncaughtText.setVisible(!!species); + this.pokemonCaughtHatchedContainer.setVisible(false); + this.pokemonCandyContainer.setVisible(false); + this.pokemonFormText.setVisible(false); + + this.setSpeciesDetails(species!, { // TODO: is this bang correct? + shiny: false, + formIndex: 0, + female: false, + variant: 0, + }); + this.pokemonSprite.setTint(0x000000); + } + } + + setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}, forceUpdate?: boolean): void { + let { shiny, formIndex, female, variant } = options; + const forSeen: boolean = options.forSeen ?? false; + const oldProps = species ? this.starterAttributes : null; + + // We will only update the sprite if there is a change to form, shiny/variant + // or gender for species with gender sprite differences + const shouldUpdateSprite = (species?.genderDiffs && !isNullOrUndefined(female)) + || !isNullOrUndefined(formIndex) || !isNullOrUndefined(shiny) || !isNullOrUndefined(variant) || forceUpdate; + + if (this.activeTooltip === "CANDY") { + if (this.species && this.pokemonCandyContainer.visible) { + const { currentFriendship, friendshipCap } = this.getFriendship(this.species.speciesId); + globalScene.ui.editTooltip("", `${currentFriendship}/${friendshipCap}`); + } else { + globalScene.ui.hideTooltip(); + } + } + + if (species?.forms?.find(f => f.formKey === "female")) { + if (female !== undefined) { + formIndex = female ? 1 : 0; + } else if (formIndex !== undefined) { + female = formIndex === 1; + } + } + + if (species) { + // Only assign shiny, female, and variant if they are undefined + if (shiny === undefined) { + shiny = oldProps?.shiny ?? false; + } + if (female === undefined) { + female = oldProps?.female ?? false; + } + if (variant === undefined) { + variant = oldProps?.variant ?? 0; + } + if (formIndex === undefined) { + formIndex = oldProps?.form ?? 0; + } + } + + this.pokemonSprite.setVisible(false); + + if (this.assetLoadCancelled) { + this.assetLoadCancelled.value = true; + this.assetLoadCancelled = null; + } + + if (species) { + const dexEntry = globalScene.gameData.dexData[species.speciesId]; + + const caughtAttr = this.isCaught(dexEntry); + + if (!caughtAttr) { + const props = this.starterAttributes; + + if (shiny === undefined || shiny !== props.shiny) { + shiny = props.shiny; + } + if (formIndex === undefined || formIndex !== props.form) { + formIndex = props.form; + } + if (female === undefined || female !== props.female) { + female = props.female; + } + if (variant === undefined || variant !== props.variant) { + variant = props.variant; + } + } + + const isFormCaught = this.isFormCaught(); + + this.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default? + this.pokemonNumberText.setColor(this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, false)); + this.pokemonNumberText.setShadowColor(this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, true)); + + + const assetLoadCancelled = new BooleanHolder(false); + this.assetLoadCancelled = assetLoadCancelled; + + if (shouldUpdateSprite) { + const back = this.showBackSprite ? true : false; + species.loadAssets(female!, formIndex, shiny, variant as Variant, true, back).then(() => { // TODO: is this bang correct? + if (assetLoadCancelled.value) { + return; + } + this.assetLoadCancelled = null; + this.speciesLoaded.set(species.speciesId, true); + this.pokemonSprite.play(species.getSpriteKey(female!, formIndex, shiny, variant, back)); // TODO: is this bang correct? + this.pokemonSprite.setPipelineData("shiny", shiny); + this.pokemonSprite.setPipelineData("variant", variant); + this.pokemonSprite.setPipelineData("spriteKey", species.getSpriteKey(female!, formIndex, shiny, variant, back)); // TODO: is this bang correct? + this.pokemonSprite.setVisible(!this.statsMode); + }); + } else { + this.pokemonSprite.setVisible(!this.statsMode); + } + + const currentFilteredContainer = this.filteredStarterContainers.find(p => p.species.speciesId === species.speciesId); + if (currentFilteredContainer) { + const starterSprite = currentFilteredContainer.icon as Phaser.GameObjects.Sprite; + starterSprite.setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female!, formIndex, shiny, variant)); + currentFilteredContainer.checkIconId(female, formIndex, shiny, variant); + } + + const isNonShinyCaught = !!(caughtAttr & DexAttr.NON_SHINY); + const isShinyCaught = !!(caughtAttr & DexAttr.SHINY); + + this.canCycleShiny = isNonShinyCaught && isShinyCaught; + + const isMaleCaught = !!(caughtAttr & DexAttr.MALE); + const isFemaleCaught = !!(caughtAttr & DexAttr.FEMALE); + this.canCycleGender = isMaleCaught && isFemaleCaught; + + // If the dev option for the dex is selected, all forms can be cycled through + this.canCycleForm = globalScene.dexForDevs ? species.forms.length > 1 : + species.forms.filter(f => f.isStarterSelectable).filter(f => f).length > 1; + + if (caughtAttr && species.malePercent !== null) { + const gender = !female ? Gender.MALE : Gender.FEMALE; + this.pokemonGenderText.setText(getGenderSymbol(gender)); + this.pokemonGenderText.setColor(getGenderColor(gender)); + this.pokemonGenderText.setShadowColor(getGenderColor(gender, true)); + } else { + this.pokemonGenderText.setText(""); + } + + if (caughtAttr) { + if (isFormCaught) { + this.species.loadAssets(female!, formIndex, shiny, variant as Variant, true).then(() => { + const crier = (this.species.forms && this.species.forms.length > 0) ? this.species.forms[formIndex ?? this.formIndex] : this.species; + crier.cry(); + }); + + this.pokemonSprite.clearTint(); + } else { + this.pokemonSprite.setTint(0x000000); + } + } + + if (caughtAttr || forSeen) { + const speciesForm = getPokemonSpeciesForm(species.speciesId, formIndex!); // TODO: is the bang correct? + this.setTypeIcons(speciesForm.type1, speciesForm.type2); + this.pokemonFormText.setText(this.getFormString((speciesForm as PokemonForm).formKey, species)); + + } else { + this.setTypeIcons(null, null); + this.pokemonFormText.setText(""); + } + } else { + this.shinyOverlay.setVisible(false); + this.pokemonNumberText.setColor(this.getTextColor(TextStyle.SUMMARY)); + this.pokemonNumberText.setShadowColor(this.getTextColor(TextStyle.SUMMARY, true)); + this.pokemonGenderText.setText(""); + this.setTypeIcons(null, null); + } + + this.updateInstructions(); + } + + setTypeIcons(type1: Type | null, type2: Type | null): void { + if (type1 !== null) { + this.type1Icon.setVisible(true); + this.type1Icon.setFrame(Type[type1].toLowerCase()); + } else { + this.type1Icon.setVisible(false); + } + if (type2 !== null) { + this.type2Icon.setVisible(true); + this.type2Icon.setFrame(Type[type2].toLowerCase()); + } else { + this.type2Icon.setVisible(false); + } + } + + + /** + * Creates a temporary dex attr props that will be used to display the correct shiny, variant, and form based on this.starterAttributes + * + * @param speciesId the id of the species to get props for + * @returns the dex props + */ + getCurrentDexProps(speciesId: number): bigint { + let props = 0n; + const caughtAttr = globalScene.gameData.dexData[speciesId].caughtAttr; + + /* this checks the gender of the pokemon; this works by checking a) that the starter preferences for the species exist, and if so, is it female. If so, it'll add DexAttr.FEMALE to our temp props + * It then checks b) if the caughtAttr for the pokemon is female and NOT male - this means that the ONLY gender we've gotten is female, and we need to add DexAttr.FEMALE to our temp props + * If neither of these pass, we add DexAttr.MALE to our temp props + */ + if (this.starterAttributes?.female || ((caughtAttr & DexAttr.FEMALE) > 0n && (caughtAttr & DexAttr.MALE) === 0n)) { + props += DexAttr.FEMALE; + } else { + props += DexAttr.MALE; + } + /* This part is very similar to above, but instead of for gender, it checks for shiny within starter preferences. + * If they're not there, it enables shiny state by default if any shiny was caught + */ + if (this.starterAttributes?.shiny || ((caughtAttr & DexAttr.SHINY) > 0n && this.starterAttributes?.shiny !== false)) { + props += DexAttr.SHINY; + if (this.starterAttributes?.variant !== undefined) { + props += BigInt(Math.pow(2, this.starterAttributes?.variant)) * DexAttr.DEFAULT_VARIANT; + } else { + /* This calculates the correct variant if there's no starter preferences for it. + * This gets the highest tier variant that you've caught and adds it to the temp props + */ + if ((caughtAttr & DexAttr.VARIANT_3) > 0) { + props += DexAttr.VARIANT_3; + } else if ((caughtAttr & DexAttr.VARIANT_2) > 0) { + props += DexAttr.VARIANT_2; + } else { + props += DexAttr.DEFAULT_VARIANT; + } + } + } else { + props += DexAttr.NON_SHINY; + props += DexAttr.DEFAULT_VARIANT; // we add the default variant here because non shiny versions are listed as default variant + } + if (this.starterAttributes?.form) { // this checks for the form of the pokemon + props += BigInt(Math.pow(2, this.starterAttributes?.form)) * DexAttr.DEFAULT_FORM; + } else { + // Get the first unlocked form + props += globalScene.gameData.getFormAttr(globalScene.gameData.getFormIndex(caughtAttr)); + } + + return props; + } + + toggleStatsMode(on?: boolean): void { + if (on === undefined) { + on = !this.statsMode; + } + if (on) { + this.showStats(); + this.statsMode = true; + this.pokemonSprite.setVisible(false); + } else { + this.statsMode = false; + this.statsContainer.setVisible(false); + this.pokemonSprite.setVisible(true); + //@ts-ignore + this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. !?!? + } + } + + showStats(): void { + if (!this.speciesStarterDexEntry) { + return; + } + + this.statsContainer.setVisible(true); + + this.statsContainer.updateIvs(this.speciesStarterDexEntry.ivs); + } + + clearText() { + this.starterSelectMessageBoxContainer.setVisible(false); + super.clearText(); + } + + hideInstructions(): void { + this.candyUpgradeIconElement.setVisible(false); + this.candyUpgradeLabel.setVisible(false); + this.shinyIconElement.setVisible(false); + this.shinyLabel.setVisible(false); + this.formIconElement.setVisible(false); + this.formLabel.setVisible(false); + this.genderIconElement.setVisible(false); + this.genderLabel.setVisible(false); + this.variantIconElement.setVisible(false); + this.variantLabel.setVisible(false); + } + + clear(): void { + super.clear(); + + this.cursor = -1; + this.hideInstructions(); + this.activeTooltip = undefined; + globalScene.ui.hideTooltip(); + + this.starterSelectContainer.setVisible(false); + this.blockInput = false; + + this.showBackSprite = false; + this.showBackSpriteLabel.setText(i18next.t("pokedexUiHandler:showBackSprite")); + + if (this.statsMode) { + this.toggleStatsMode(false); + } + } + + checkIconId(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, female: boolean, formIndex: number, shiny: boolean, variant: number) { + if (icon.frame.name !== species.getIconId(female, formIndex, shiny, variant)) { + console.log(`${species.name}'s icon ${icon.frame.name} does not match getIconId with female: ${female}, formIndex: ${formIndex}, shiny: ${shiny}, variant: ${variant}`); + icon.setTexture(species.getIconAtlasKey(formIndex, false, variant)); + icon.setFrame(species.getIconId(female, formIndex, false, variant)); + } + } +} diff --git a/src/ui/pokedex-scan-ui-handler.ts b/src/ui/pokedex-scan-ui-handler.ts new file mode 100644 index 00000000000..0fc7171842a --- /dev/null +++ b/src/ui/pokedex-scan-ui-handler.ts @@ -0,0 +1,191 @@ +import type { InputFieldConfig } from "./form-modal-ui-handler"; +import { FormModalUiHandler } from "./form-modal-ui-handler"; +import type { ModalConfig } from "./modal-ui-handler"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { OptionSelectItem } from "./abstact-option-select-ui-handler"; +import { isNullOrUndefined } from "#app/utils"; +import { Mode } from "./ui"; +import { FilterTextRow } from "./filter-text"; +import { allAbilities } from "#app/data/ability"; +import { allMoves } from "#app/data/move"; +import { allSpecies } from "#app/data/pokemon-species"; +import i18next from "i18next"; + +export default class PokedexScanUiHandler extends FormModalUiHandler { + + keys: string[]; + reducedKeys: string[]; + parallelKeys: string[]; + nameKeys: string[]; + moveKeys: string[]; + abilityKeys: string[]; + row: number; + + constructor(mode) { + super(mode); + } + + setup() { + super.setup(); + + this.nameKeys = allSpecies.map(a => a.name).filter((value, index, self) => self.indexOf(value) === index); + this.moveKeys = allMoves.map(a => a.name); + this.abilityKeys = allAbilities.map(a => a.name); + } + + getModalTitle(config?: ModalConfig): string { + return i18next.t("pokedexUiHandler:scanChooseOption"); + } + + getWidth(config?: ModalConfig): number { + return 300; + } + + getMargin(config?: ModalConfig): [number, number, number, number] { + return [ 0, 0, 48, 0 ]; + } + + getButtonLabels(config?: ModalConfig): string[] { + return [ i18next.t("pokedexUiHandler:scanSelect"), i18next.t("pokedexUiHandler:scanCancel") ]; + } + + getReadableErrorMessage(error: string): string { + const colonIndex = error?.indexOf(":"); + if (colonIndex > 0) { + error = error.slice(0, colonIndex); + } + + return super.getReadableErrorMessage(error); + } + + override getInputFieldConfigs(): InputFieldConfig[] { + switch (this.row) { + case FilterTextRow.NAME: { + return [{ label: i18next.t("pokedexUiHandler:scanLabelName") }]; + } + case FilterTextRow.MOVE_1: + case FilterTextRow.MOVE_2: { + return [{ label: i18next.t("pokedexUiHandler:scanLabelMove") }]; + } + case FilterTextRow.ABILITY_1:{ + return [{ label: i18next.t("pokedexUiHandler:scanLabelAbility") }]; + } + case FilterTextRow.ABILITY_2: { + return [{ label: i18next.t("pokedexUiHandler:scanLabelPassive") }]; + } + default: { + return [{ label: "" }]; + } + } + + } + + reduceKeys(): void { + switch (this.row) { + case FilterTextRow.NAME: { + this.reducedKeys = this.nameKeys; + break; + } + case FilterTextRow.MOVE_1: + case FilterTextRow.MOVE_2: { + this.reducedKeys = this.moveKeys; + break; + } + case FilterTextRow.ABILITY_1: + case FilterTextRow.ABILITY_2: { + this.reducedKeys = this.abilityKeys; + break; + } + default: { + this.reducedKeys = this.keys; + } + } + } + + + // args[2] is an index of FilterTextRow + show(args: any[]): boolean { + this.row = args[2]; + const ui = this.getUi(); + const hasTitle = !!this.getModalTitle(); + this.updateFields(this.getInputFieldConfigs(), hasTitle); + this.updateContainer(args[0] as ModalConfig); + const input = this.inputs[0]; + input.setMaxLength(255); + + this.reduceKeys(); + + input.on("keydown", (inputObject, evt: KeyboardEvent) => { + if ([ "escape", "space" ].some((v) => v === evt.key.toLowerCase() || v === evt.code.toLowerCase()) && ui.getMode() === Mode.AUTO_COMPLETE) { + // Delete autocomplete list and recovery focus. + inputObject.on("blur", () => inputObject.node.focus(), { once: true }); + ui.revertMode(); + } + }); + + input.on("textchange", (inputObject, evt: InputEvent) => { + // Delete autocomplete. + if (ui.getMode() === Mode.AUTO_COMPLETE) { + ui.revertMode(); + } + + let options: OptionSelectItem[] = []; + const filteredKeys = this.reducedKeys.filter((command) => command.toLowerCase().includes(inputObject.text.toLowerCase())); + if (inputObject.text !== "" && filteredKeys.length > 0) { + options = filteredKeys.slice(0).map((value) => { + return { + label: value, + handler: () => { + if (!isNullOrUndefined(evt.data) || evt.inputType?.toLowerCase() === "deletecontentbackward") { + inputObject.setText(value); + } + ui.revertMode(); + return true; + } + }; + }); + } + + if (options.length > 0) { + const modalOpts = { + options: options, + maxOptions: 5, + modalContainer: this.modalContainer + }; + ui.setOverlayMode(Mode.AUTO_COMPLETE, modalOpts); + } + + }); + + if (super.show(args)) { + const config = args[0] as ModalConfig; + this.inputs[0].resize(1150, 116); + this.inputContainers[0].list[0].width = 200; + if (args[1] && typeof (args[1] as PlayerPokemon).getNameToRender === "function") { + this.inputs[0].text = (args[1] as PlayerPokemon).getNameToRender(); + } else { + this.inputs[0].text = args[1]; + } + this.submitAction = (_) => { + if (ui.getMode() === Mode.POKEDEX_SCAN) { + this.sanitizeInputs(); + const sanitizedName = btoa(unescape(encodeURIComponent(this.inputs[0].text))); + config.buttonActions[0](sanitizedName); + return true; + } + return false; + }; + return true; + } + return false; + } + + clear(): void { + super.clear(); + + // Clearing the labels so they don't appear again and overlap + this.formLabels.forEach(label => { + label.destroy(); + }); + } +} diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts new file mode 100644 index 00000000000..410bb53906a --- /dev/null +++ b/src/ui/pokedex-ui-handler.ts @@ -0,0 +1,1877 @@ +import type { Variant } from "#app/data/variant"; +import { getVariantTint, getVariantIcon } from "#app/data/variant"; +import { argbFromRgba } from "@material/material-color-utilities"; +import i18next from "i18next"; +import { starterColors } from "#app/battle-scene"; +import { speciesEggMoves } from "#app/data/balance/egg-moves"; +import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; +import type { PokemonForm } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; +import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; +import { catchableSpecies } from "#app/data/balance/biomes"; +import { Type } from "#enums/type"; +import type { DexAttrProps, DexEntry, StarterMoveset, StarterAttributes, StarterPreferences } from "#app/system/game-data"; +import { AbilityAttr, DexAttr, StarterPrefs } from "#app/system/game-data"; +import MessageUiHandler from "#app/ui/message-ui-handler"; +import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; +import { TextStyle, addTextObject } from "#app/ui/text"; +import { Mode } from "#app/ui/ui"; +import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; +import { Passive as PassiveAttr } from "#enums/passive"; +import type { Moves } from "#enums/moves"; +import type { Species } from "#enums/species"; +import { Button } from "#enums/buttons"; +import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown"; +import { PokedexMonContainer } from "#app/ui/pokedex-mon-container"; +import { DropDownColumn, FilterBar } from "#app/ui/filter-bar"; +import { ScrollBar } from "#app/ui/scroll-bar"; +import { Abilities } from "#enums/abilities"; +import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters"; +import { BooleanHolder, fixedInt, getLocalizedSpriteKey, padInt, randIntRange, rgbHexToRgba } from "#app/utils"; +import type { Nature } from "#enums/nature"; +import { addWindow } from "./ui-theme"; +import type { OptionSelectConfig } from "./abstact-option-select-ui-handler"; +import { FilterText, FilterTextRow } from "./filter-text"; +import { allAbilities } from "#app/data/ability"; +import type { PassiveAbilities } from "#app/data/balance/passives"; +import { starterPassiveAbilities } from "#app/data/balance/passives"; +import { allMoves } from "#app/data/move"; +import { speciesTmMoves } from "#app/data/balance/tms"; +import { pokemonStarters } from "#app/data/balance/pokemon-evolutions"; +import { Biome } from "#enums/biome"; +import { globalScene } from "#app/global-scene"; + + +interface LanguageSetting { + starterInfoTextSize: string, + instructionTextSize: string, + starterInfoXPos?: number, + starterInfoYOffset?: number +} + +const languageSettings: { [key: string]: LanguageSetting } = { + "en":{ + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, + "de":{ + starterInfoTextSize: "48px", + instructionTextSize: "35px", + starterInfoXPos: 33, + }, + "es-ES":{ + starterInfoTextSize: "56px", + instructionTextSize: "35px", + }, + "fr":{ + starterInfoTextSize: "54px", + instructionTextSize: "38px", + }, + "it":{ + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, + "pt_BR":{ + starterInfoTextSize: "47px", + instructionTextSize: "38px", + starterInfoXPos: 33, + }, + "zh":{ + starterInfoTextSize: "47px", + instructionTextSize: "38px", + starterInfoYOffset: 1, + starterInfoXPos: 24, + }, + "pt":{ + starterInfoTextSize: "48px", + instructionTextSize: "42px", + starterInfoXPos: 33, + }, + "ko":{ + starterInfoTextSize: "52px", + instructionTextSize: "38px", + }, + "ja":{ + starterInfoTextSize: "51px", + instructionTextSize: "38px", + }, + "ca-ES":{ + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, +}; + + +enum FilterTextOptions{ + NAME, + MOVE_1, + MOVE_2, + ABILITY_1, + ABILITY_2, +} + + +const valueReductionMax = 2; + +// Position of UI elements +const filterBarHeight = 17; +const speciesContainerX = 143; + +/** + * Calculates the starter position for a Pokemon of a given UI index + * @param index UI index to calculate the starter position of + * @returns An interface with an x and y property + */ +function calcStarterPosition(index: number, scrollCursor:number = 0): {x: number, y: number} { + const yOffset = 13; + const height = 17; + const x = (index % 9) * 18; + const y = yOffset + (Math.floor(index / 9) - scrollCursor) * height; + + return { x: x, y: y }; +} + +interface SpeciesDetails { + shiny?: boolean, + formIndex?: number + female?: boolean, + variant?: Variant, + abilityIndex?: number, + natureIndex?: number, + forSeen?: boolean, // default = false +} + +export default class PokedexUiHandler extends MessageUiHandler { + private starterSelectContainer: Phaser.GameObjects.Container; + private starterSelectScrollBar: ScrollBar; + private filterBarContainer: Phaser.GameObjects.Container; + private filterBar: FilterBar; + private pokemonContainers: PokedexMonContainer[] = []; + private filteredPokemonContainers: PokedexMonContainer[] = []; + private validPokemonContainers: PokedexMonContainer[] = []; + private pokemonNumberText: Phaser.GameObjects.Text; + private pokemonSprite: Phaser.GameObjects.Sprite; + private pokemonNameText: Phaser.GameObjects.Text; + private type1Icon: Phaser.GameObjects.Sprite; + private type2Icon: Phaser.GameObjects.Sprite; + + private starterSelectMessageBox: Phaser.GameObjects.NineSlice; + private starterSelectMessageBoxContainer: Phaser.GameObjects.Container; + + private filterMode: boolean; + private filterBarCursor: number = 0; + private starterMoveset: StarterMoveset | null; + private scrollCursor: number; + + private allSpecies: PokemonSpecies[] = []; + private lastSpecies: PokemonSpecies; + private speciesLoaded: Map = new Map(); + private pokerusSpecies: PokemonSpecies[] = []; + private speciesStarterDexEntry: DexEntry | null; + private speciesStarterMoves: Moves[]; + + private assetLoadCancelled: BooleanHolder | null; + public cursorObj: Phaser.GameObjects.Image; + private pokerusCursorObjs: Phaser.GameObjects.Image[]; + + private iconAnimHandler: PokemonIconAnimHandler; + + private starterPreferences: StarterPreferences; + + protected blockInput: boolean = false; + + // for text filters + private readonly textPadding = 8; + private readonly defaultMessageBoxWidth = 220; + private readonly defaultWordWrapWidth = 1224; + private menuMessageBoxContainer: Phaser.GameObjects.Container; + private menuMessageBox: Phaser.GameObjects.NineSlice; + private dialogueMessageBox: Phaser.GameObjects.NineSlice; + protected manageDataConfig: OptionSelectConfig; + private filterTextOptions: FilterTextOptions[]; + protected optionSelectText: Phaser.GameObjects.Text; + protected scale: number = 0.1666666667; + private menuBg: Phaser.GameObjects.NineSlice; + + private filterTextContainer: Phaser.GameObjects.Container; + private filterText: FilterText; + private filterTextMode: boolean; + private filterTextCursor: number = 0; + + private showDecorations: boolean = false; + private goFilterIconElement1: Phaser.GameObjects.Sprite; + private goFilterIconElement2: Phaser.GameObjects.Sprite; + private goFilterLabel: Phaser.GameObjects.Text; + private toggleDecorationsIconElement: Phaser.GameObjects.Sprite; + private toggleDecorationsLabel: Phaser.GameObjects.Text; + + constructor() { + super(Mode.POKEDEX); + } + + setup() { + const ui = this.getUi(); + const currentLanguage = i18next.resolvedLanguage ?? "en"; + const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)) ?? "en"; + const textSettings = languageSettings[langSettingKey]; + + this.starterSelectContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); + this.starterSelectContainer.setVisible(false); + ui.add(this.starterSelectContainer); + + const bgColor = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x006860); + bgColor.setOrigin(0, 0); + this.starterSelectContainer.add(bgColor); + + const pokemonContainerWindow = addWindow(speciesContainerX, filterBarHeight + 1, 175, 161); + const pokemonContainerBg = globalScene.add.image(speciesContainerX + 1, filterBarHeight + 2, "starter_container_bg"); + pokemonContainerBg.setOrigin(0, 0); + this.starterSelectContainer.add(pokemonContainerBg); + this.starterSelectContainer.add(pokemonContainerWindow); + + + // Create and initialise filter text fields + this.filterTextContainer = globalScene.add.container(0, 0); + this.filterText = new FilterText(1, filterBarHeight + 2, 140, 100, this.updateStarters); + + this.filterText.addFilter(FilterTextRow.NAME, i18next.t("filterText:nameField")); + this.filterText.addFilter(FilterTextRow.MOVE_1, i18next.t("filterText:move1Field")); + this.filterText.addFilter(FilterTextRow.MOVE_2, i18next.t("filterText:move2Field")); + this.filterText.addFilter(FilterTextRow.ABILITY_1, i18next.t("filterText:ability1Field")); + this.filterText.addFilter(FilterTextRow.ABILITY_2, i18next.t("filterText:ability2Field")); + + this.filterTextContainer.add(this.filterText); + this.starterSelectContainer.add(this.filterTextContainer); + + + // Create and initialise filter bar + this.filterBarContainer = globalScene.add.container(0, 0); + this.filterBar = new FilterBar(speciesContainerX, 1, 175, filterBarHeight, 2, 0, 6); + + // gen filter + const genOptions: DropDownOption[] = [ + new DropDownOption(1, new DropDownLabel(i18next.t("pokedexUiHandler:gen1"))), + new DropDownOption(2, new DropDownLabel(i18next.t("pokedexUiHandler:gen2"))), + new DropDownOption(3, new DropDownLabel(i18next.t("pokedexUiHandler:gen3"))), + new DropDownOption(4, new DropDownLabel(i18next.t("pokedexUiHandler:gen4"))), + new DropDownOption(5, new DropDownLabel(i18next.t("pokedexUiHandler:gen5"))), + new DropDownOption(6, new DropDownLabel(i18next.t("pokedexUiHandler:gen6"))), + new DropDownOption(7, new DropDownLabel(i18next.t("pokedexUiHandler:gen7"))), + new DropDownOption(8, new DropDownLabel(i18next.t("pokedexUiHandler:gen8"))), + new DropDownOption(9, new DropDownLabel(i18next.t("pokedexUiHandler:gen9"))), + ]; + const genDropDown: DropDown = new DropDown(0, 0, genOptions, this.updateStarters, DropDownType.HYBRID); + this.filterBar.addFilter(DropDownColumn.GEN, i18next.t("filterBar:genFilter"), genDropDown); + + // type filter + const typeKeys = Object.keys(Type).filter(v => isNaN(Number(v))); + const typeOptions: DropDownOption[] = []; + typeKeys.forEach((type, index) => { + if (index === 0 || index === 19) { + return; + } + const typeSprite = globalScene.add.sprite(0, 0, getLocalizedSpriteKey("types")); + typeSprite.setScale(0.5); + typeSprite.setFrame(type.toLowerCase()); + typeOptions.push(new DropDownOption( index, new DropDownLabel("", typeSprite))); + }); + this.filterBar.addFilter(DropDownColumn.TYPES, i18next.t("filterBar:typeFilter"), new DropDown(0, 0, typeOptions, this.updateStarters, DropDownType.HYBRID, 0.5)); + + // biome filter, making an entry in the dropdown for each biome + const biomeOptions = Object.values(Biome) + .filter((value) => typeof value === "number") // Filter numeric values from the enum + .map((biomeValue, index) => + new DropDownOption( index, new DropDownLabel(i18next.t(`biome:${Biome[biomeValue].toUpperCase()}`))) + ); + biomeOptions.push(new DropDownOption( biomeOptions.length, new DropDownLabel(i18next.t("filterBar:uncatchable")))); + const biomeDropDown: DropDown = new DropDown(0, 0, biomeOptions, this.updateStarters, DropDownType.HYBRID); + this.filterBar.addFilter(DropDownColumn.BIOME, i18next.t("filterBar:biomeFilter"), biomeDropDown); + + // caught filter + const shiny1Sprite = globalScene.add.sprite(0, 0, "shiny_icons"); + shiny1Sprite.setOrigin(0.15, 0.2); + shiny1Sprite.setScale(0.6); + shiny1Sprite.setFrame(getVariantIcon(0)); + shiny1Sprite.setTint(getVariantTint(0)); + const shiny2Sprite = globalScene.add.sprite(0, 0, "shiny_icons"); + shiny2Sprite.setOrigin(0.15, 0.2); + shiny2Sprite.setScale(0.6); + shiny2Sprite.setFrame(getVariantIcon(1)); + shiny2Sprite.setTint(getVariantTint(1)); + const shiny3Sprite = globalScene.add.sprite(0, 0, "shiny_icons"); + shiny3Sprite.setOrigin(0.15, 0.2); + shiny3Sprite.setScale(0.6); + shiny3Sprite.setFrame(getVariantIcon(2)); + shiny3Sprite.setTint(getVariantTint(2)); + + const caughtOptions = [ + new DropDownOption("SHINY3", new DropDownLabel("", shiny3Sprite)), + new DropDownOption("SHINY2", new DropDownLabel("", shiny2Sprite)), + new DropDownOption("SHINY", new DropDownLabel("", shiny1Sprite)), + new DropDownOption("NORMAL", new DropDownLabel(i18next.t("filterBar:normal"))), + new DropDownOption("UNCAUGHT", new DropDownLabel(i18next.t("filterBar:uncaught"))) + ]; + + this.filterBar.addFilter(DropDownColumn.CAUGHT, i18next.t("filterBar:caughtFilter"), new DropDown(0, 0, caughtOptions, this.updateStarters, DropDownType.HYBRID)); + + // unlocks filter + const passiveLabels = [ + new DropDownLabel(i18next.t("filterBar:passive"), undefined, DropDownState.OFF), + new DropDownLabel(i18next.t("filterBar:passiveUnlocked"), undefined, DropDownState.ON), + new DropDownLabel(i18next.t("filterBar:passiveUnlockable"), undefined, DropDownState.UNLOCKABLE), + new DropDownLabel(i18next.t("filterBar:passiveLocked"), undefined, DropDownState.EXCLUDE), + ]; + + const costReductionLabels = [ + new DropDownLabel(i18next.t("filterBar:costReduction"), undefined, DropDownState.OFF), + new DropDownLabel(i18next.t("filterBar:costReductionUnlocked"), undefined, DropDownState.ON), + new DropDownLabel(i18next.t("filterBar:costReductionUnlockable"), undefined, DropDownState.UNLOCKABLE), + new DropDownLabel(i18next.t("filterBar:costReductionLocked"), undefined, DropDownState.EXCLUDE), + ]; + + const unlocksOptions = [ + new DropDownOption("PASSIVE", passiveLabels), + new DropDownOption("COST_REDUCTION", costReductionLabels), + ]; + + this.filterBar.addFilter(DropDownColumn.UNLOCKS, i18next.t("filterBar:unlocksFilter"), new DropDown(0, 0, unlocksOptions, this.updateStarters, DropDownType.RADIAL)); + + // misc filter + const starters = [ + new DropDownLabel(i18next.t("filterBar:starter"), undefined, DropDownState.OFF), + new DropDownLabel(i18next.t("filterBar:isStarter"), undefined, DropDownState.ON), + new DropDownLabel(i18next.t("filterBar:notStarter"), undefined, DropDownState.EXCLUDE), + ]; + const favoriteLabels = [ + new DropDownLabel(i18next.t("filterBar:favorite"), undefined, DropDownState.OFF), + new DropDownLabel(i18next.t("filterBar:isFavorite"), undefined, DropDownState.ON), + new DropDownLabel(i18next.t("filterBar:notFavorite"), undefined, DropDownState.EXCLUDE), + ]; + const winLabels = [ + new DropDownLabel(i18next.t("filterBar:ribbon"), undefined, DropDownState.OFF), + new DropDownLabel(i18next.t("filterBar:hasWon"), undefined, DropDownState.ON), + new DropDownLabel(i18next.t("filterBar:hasNotWon"), undefined, DropDownState.EXCLUDE), + ]; + const hiddenAbilityLabels = [ + new DropDownLabel(i18next.t("filterBar:hiddenAbility"), undefined, DropDownState.OFF), + new DropDownLabel(i18next.t("filterBar:hasHiddenAbility"), undefined, DropDownState.ON), + new DropDownLabel(i18next.t("filterBar:noHiddenAbility"), undefined, DropDownState.EXCLUDE), + ]; + const eggLabels = [ + new DropDownLabel(i18next.t("filterBar:egg"), undefined, DropDownState.OFF), + new DropDownLabel(i18next.t("filterBar:eggPurchasable"), undefined, DropDownState.ON), + ]; + const pokerusLabels = [ + new DropDownLabel(i18next.t("filterBar:pokerus"), undefined, DropDownState.OFF), + new DropDownLabel(i18next.t("filterBar:hasPokerus"), undefined, DropDownState.ON), + ]; + const miscOptions = [ + new DropDownOption("STARTER", starters), + new DropDownOption("FAVORITE", favoriteLabels), + new DropDownOption("WIN", winLabels), + new DropDownOption("HIDDEN_ABILITY", hiddenAbilityLabels), + new DropDownOption("EGG", eggLabels), + new DropDownOption("POKERUS", pokerusLabels), + ]; + this.filterBar.addFilter(DropDownColumn.MISC, i18next.t("filterBar:miscFilter"), new DropDown(0, 0, miscOptions, this.updateStarters, DropDownType.RADIAL)); + + // sort filter + const sortOptions = [ + new DropDownOption(SortCriteria.NUMBER, new DropDownLabel(i18next.t("filterBar:sortByNumber"), undefined, DropDownState.ON)), + new DropDownOption(SortCriteria.COST, new DropDownLabel(i18next.t("filterBar:sortByCost"))), + new DropDownOption(SortCriteria.CANDY, new DropDownLabel(i18next.t("filterBar:sortByCandies"))), + new DropDownOption(SortCriteria.IV, new DropDownLabel(i18next.t("filterBar:sortByIVs"))), + new DropDownOption(SortCriteria.NAME, new DropDownLabel(i18next.t("filterBar:sortByName"))), + new DropDownOption(SortCriteria.CAUGHT, new DropDownLabel(i18next.t("filterBar:sortByNumCaught"))), + new DropDownOption(SortCriteria.HATCHED, new DropDownLabel(i18next.t("filterBar:sortByNumHatched"))) + ]; + this.filterBar.addFilter(DropDownColumn.SORT, i18next.t("filterBar:sortFilter"), new DropDown(0, 0, sortOptions, this.updateStarters, DropDownType.SINGLE)); + this.filterBarContainer.add(this.filterBar); + + this.starterSelectContainer.add(this.filterBarContainer); + + // Offset the generation filter dropdown to avoid covering the filtered pokemon + this.filterBar.offsetHybridFilters(); + + if (!globalScene.uiTheme) { + pokemonContainerWindow.setVisible(false); + } + + this.iconAnimHandler = new PokemonIconAnimHandler(); + this.iconAnimHandler.setup(); + + this.pokemonNumberText = addTextObject(6, 140, "", TextStyle.SUMMARY); + this.pokemonNumberText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonNumberText); + + this.pokemonNameText = addTextObject(6, 127, "", TextStyle.SUMMARY); + this.pokemonNameText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonNameText); + + const starterBoxContainer = globalScene.add.container(speciesContainerX + 6, 9); //115 + + this.starterSelectScrollBar = new ScrollBar(161, 12, 5, pokemonContainerWindow.height - 6, 9); + + starterBoxContainer.add(this.starterSelectScrollBar); + + this.pokerusCursorObjs = new Array(POKERUS_STARTER_COUNT).fill(null).map(() => { + const cursorObj = globalScene.add.image(0, 0, "select_cursor_pokerus"); + cursorObj.setVisible(false); + cursorObj.setOrigin(0, 0); + starterBoxContainer.add(cursorObj); + return cursorObj; + }); + + this.cursorObj = globalScene.add.image(0, 0, "select_cursor"); + this.cursorObj.setOrigin(0, 0); + + starterBoxContainer.add(this.cursorObj); + + for (const species of allSpecies) { + this.speciesLoaded.set(species.speciesId, false); + this.allSpecies.push(species); + + const pokemonContainer = new PokedexMonContainer(species).setVisible(false); + this.iconAnimHandler.addOrUpdate(pokemonContainer.icon, PokemonIconAnimMode.NONE); + this.pokemonContainers.push(pokemonContainer); + starterBoxContainer.add(pokemonContainer); + } + + this.starterSelectContainer.add(starterBoxContainer); + + this.pokemonSprite = globalScene.add.sprite(96, 143, "pkmn__sub"); + this.pokemonSprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); + this.starterSelectContainer.add(this.pokemonSprite); + + this.type1Icon = globalScene.add.sprite(10, 158, getLocalizedSpriteKey("types")); + this.type1Icon.setScale(0.5); + this.type1Icon.setOrigin(0, 0); + this.starterSelectContainer.add(this.type1Icon); + + this.type2Icon = globalScene.add.sprite(10, 166, getLocalizedSpriteKey("types")); + this.type2Icon.setScale(0.5); + this.type2Icon.setOrigin(0, 0); + this.starterSelectContainer.add(this.type2Icon); + + this.starterSelectMessageBoxContainer = globalScene.add.container(0, globalScene.game.canvas.height / 6); + this.starterSelectMessageBoxContainer.setVisible(false); + this.starterSelectContainer.add(this.starterSelectMessageBoxContainer); + + this.starterSelectMessageBox = addWindow(1, -1, 318, 28); + this.starterSelectMessageBox.setOrigin(0, 1); + this.starterSelectMessageBoxContainer.add(this.starterSelectMessageBox); + + // Instruction for "C" button to toggle showDecorations + const instructionTextSize = textSettings.instructionTextSize; + + this.goFilterIconElement1 = new Phaser.GameObjects.Sprite(globalScene, 10, 2, "keyboard", "C.png"); + this.goFilterIconElement1.setName("sprite-goFilter1-icon-element"); + this.goFilterIconElement1.setScale(0.675); + this.goFilterIconElement1.setOrigin(0.0, 0.0); + this.goFilterIconElement2 = new Phaser.GameObjects.Sprite(globalScene, 20, 2, "keyboard", "V.png"); + this.goFilterIconElement2.setName("sprite-goFilter2-icon-element"); + this.goFilterIconElement2.setScale(0.675); + this.goFilterIconElement2.setOrigin(0.0, 0.0); + this.goFilterLabel = addTextObject(30, 2, i18next.t("pokedexUiHandler:goFilters"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.goFilterLabel.setName("text-goFilter-label"); + this.starterSelectContainer.add(this.goFilterIconElement1); + this.starterSelectContainer.add(this.goFilterIconElement2); + this.starterSelectContainer.add(this.goFilterLabel); + + this.toggleDecorationsIconElement = new Phaser.GameObjects.Sprite(globalScene, 10, 10, "keyboard", "R.png"); + this.toggleDecorationsIconElement.setName("sprite-toggleDecorations-icon-element"); + this.toggleDecorationsIconElement.setScale(0.675); + this.toggleDecorationsIconElement.setOrigin(0.0, 0.0); + this.toggleDecorationsLabel = addTextObject(20, 10, i18next.t("pokedexUiHandler:toggleDecorations"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.toggleDecorationsLabel.setName("text-toggleDecorations-label"); + this.starterSelectContainer.add(this.toggleDecorationsIconElement); + this.starterSelectContainer.add(this.toggleDecorationsLabel); + + this.message = addTextObject(8, 8, "", TextStyle.WINDOW, { maxLines: 2 }); + this.message.setOrigin(0, 0); + this.starterSelectMessageBoxContainer.add(this.message); + + // arrow icon for the message box + this.initPromptSprite(this.starterSelectMessageBoxContainer); + + // Filter bar sits above everything, except the tutorial overlay and message box + this.starterSelectContainer.bringToTop(this.filterBarContainer); + this.initTutorialOverlay(this.starterSelectContainer); + this.starterSelectContainer.bringToTop(this.starterSelectMessageBoxContainer); + } + + show(args: any[]): boolean { + + if (!this.starterPreferences) { + this.starterPreferences = StarterPrefs.load(); + } + + this.pokerusSpecies = getPokerusStarters(); + + // When calling with "refresh", we do not reset the cursor and filters + if (args.length >= 1 && args[0] === "refresh") { + return false; + } + + super.show(args); + + this.starterSelectContainer.setVisible(true); + + this.getUi().bringToTop(this.starterSelectContainer); + + // Making caught pokemon visible icons, etc + this.allSpecies.forEach((species, s) => { + const icon = this.pokemonContainers[s].icon; + const dexEntry = globalScene.gameData.dexData[species.speciesId]; + + this.starterPreferences[species.speciesId] = this.initStarterPrefs(species); + + if (dexEntry.caughtAttr) { + icon.clearTint(); + } else if (dexEntry.seenAttr) { + icon.setTint(0x808080); + } + + this.setUpgradeAnimation(icon, species); + }); + + this.resetFilters(); + this.updateStarters(); + + this.setFilterMode(false); + this.filterBarCursor = 0; + this.setFilterTextMode(false); + this.filterTextCursor = 0; + this.setCursor(0); + + this.filterTextContainer.setVisible(true); + + return true; + } + + /** + * Get the starter attributes for the given PokemonSpecies, after sanitizing them. + * If somehow a preference is set for a form, variant, gender, ability or nature + * that wasn't actually unlocked or is invalid it will be cleared here + * + * @param species The species to get Starter Preferences for + * @returns StarterAttributes for the species + */ + initStarterPrefs(species: PokemonSpecies): StarterAttributes { + const starterAttributes = this.starterPreferences[species.speciesId]; + const dexEntry = globalScene.gameData.dexData[species.speciesId]; + const starterData = globalScene.gameData.starterData[species.speciesId]; + + // no preferences or Pokemon wasn't caught, return empty attribute + if (!starterAttributes || !dexEntry.caughtAttr) { + return {}; + } + + const caughtAttr = dexEntry.caughtAttr; + + const hasShiny = caughtAttr & DexAttr.SHINY; + const hasNonShiny = caughtAttr & DexAttr.NON_SHINY; + if (starterAttributes.shiny && !hasShiny) { + // shiny form wasn't unlocked, purging shiny and variant setting + delete starterAttributes.shiny; + delete starterAttributes.variant; + } else if (starterAttributes.shiny === false && !hasNonShiny) { + // non shiny form wasn't unlocked, purging shiny setting + delete starterAttributes.shiny; + } + + if (starterAttributes.variant !== undefined) { + const unlockedVariants = [ + hasShiny && caughtAttr & DexAttr.DEFAULT_VARIANT, + hasShiny && caughtAttr & DexAttr.VARIANT_2, + hasShiny && caughtAttr & DexAttr.VARIANT_3 + ]; + if (isNaN(starterAttributes.variant) || starterAttributes.variant < 0 || !unlockedVariants[starterAttributes.variant]) { + // variant value is invalid or requested variant wasn't unlocked, purging setting + delete starterAttributes.variant; + } + } + + if (starterAttributes.female !== undefined) { + if (!(starterAttributes.female ? caughtAttr & DexAttr.FEMALE : caughtAttr & DexAttr.MALE)) { + // requested gender wasn't unlocked, purging setting + delete starterAttributes.female; + } + } + + if (starterAttributes.ability !== undefined) { + const speciesHasSingleAbility = species.ability2 === species.ability1; + const abilityAttr = starterData.abilityAttr; + const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1; + const hasAbility2 = abilityAttr & AbilityAttr.ABILITY_2; + const hasHiddenAbility = abilityAttr & AbilityAttr.ABILITY_HIDDEN; + // Due to a past bug it is possible that some Pokemon with a single ability have the ability2 flag + // In this case, we only count ability2 as valid if ability1 was not unlocked, otherwise we ignore it + const unlockedAbilities = [ + hasAbility1, + speciesHasSingleAbility ? hasAbility2 && !hasAbility1 : hasAbility2, + hasHiddenAbility + ]; + if (!unlockedAbilities[starterAttributes.ability]) { + // requested ability wasn't unlocked, purging setting + delete starterAttributes.ability; + } + } + + const selectedForm = starterAttributes.form; + if (selectedForm !== undefined && (!species.forms[selectedForm]?.isStarterSelectable || !(caughtAttr & globalScene.gameData.getFormAttr(selectedForm)))) { + // requested form wasn't unlocked/isn't a starter form, purging setting + delete starterAttributes.form; + } + + if (starterAttributes.nature !== undefined) { + const unlockedNatures = globalScene.gameData.getNaturesForAttr(dexEntry.natureAttr); + if (unlockedNatures.indexOf(starterAttributes.nature as unknown as Nature) < 0) { + // requested nature wasn't unlocked, purging setting + delete starterAttributes.nature; + } + } + + return starterAttributes; + } + + /** + * Set the selections for all filters to their default starting value + */ + resetFilters() : void { + this.filterBar.setValsToDefault(); + this.filterText.setValsToDefault(); + } + + showText(text: string, delay?: number, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number, moveToTop?: boolean) { + super.showText(text, delay, callback, callbackDelay, prompt, promptDelay); + + const singleLine = text?.indexOf("\n") === -1; + + this.starterSelectMessageBox.setSize(318, singleLine ? 28 : 42); + + if (moveToTop) { + this.starterSelectMessageBox.setOrigin(0, 0); + this.starterSelectMessageBoxContainer.setY(0); + this.message.setY(4); + } else { + this.starterSelectMessageBoxContainer.setY(globalScene.game.canvas.height / 6); + this.starterSelectMessageBox.setOrigin(0, 1); + this.message.setY(singleLine ? -22 : -37); + } + + this.starterSelectMessageBoxContainer.setVisible(!!text?.length); + } + + /** + * Determines if 'Icon' based upgrade notifications should be shown + * @returns true if upgrade notifications are enabled and set to display an 'Icon' + */ + isUpgradeIconEnabled(): boolean { + return globalScene.candyUpgradeNotification !== 0 && globalScene.candyUpgradeDisplay === 0; + } + /** + * Determines if 'Animation' based upgrade notifications should be shown + * @returns true if upgrade notifications are enabled and set to display an 'Animation' + */ + isUpgradeAnimationEnabled(): boolean { + return globalScene.candyUpgradeNotification !== 0 && globalScene.candyUpgradeDisplay === 1; + } + + getStarterSpeciesId(speciesId): number { + if (speciesStarterCosts.hasOwnProperty(speciesId)) { + return speciesId; + } else { + return pokemonStarters[speciesId]; + } + } + + /** + * Determines if a passive upgrade is available for the given species ID + * @param speciesId The ID of the species to check the passive of + * @returns true if the user has enough candies and a passive has not been unlocked already + */ + isPassiveAvailable(speciesId: number): boolean { + // Get this species ID's starter data + const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)]; + + return starterData.candyCount >= getPassiveCandyCount(speciesStarterCosts[this.getStarterSpeciesId(speciesId)]) + && !(starterData.passiveAttr & PassiveAttr.UNLOCKED); + } + + /** + * Determines if a value reduction upgrade is available for the given species ID + * @param speciesId The ID of the species to check the value reduction of + * @returns true if the user has enough candies and all value reductions have not been unlocked already + */ + isValueReductionAvailable(speciesId: number): boolean { + // Get this species ID's starter data + const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)]; + + return starterData.candyCount >= getValueReductionCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(speciesId)])[starterData.valueReduction] + && starterData.valueReduction < valueReductionMax; + } + + /** + * Determines if an same species egg can be bought for the given species ID + * @param speciesId The ID of the species to check the value reduction of + * @returns true if the user has enough candies + */ + isSameSpeciesEggAvailable(speciesId: number): boolean { + // Get this species ID's starter data + const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)]; + + return starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(speciesId)]); + } + + /** + * Sets a bounce animation if enabled and the Pokemon has an upgrade + * @param icon {@linkcode Phaser.GameObjects.GameObject} to animate + * @param species {@linkcode PokemonSpecies} of the icon used to check for upgrades + * @param startPaused Should this animation be paused after it is added? + */ + setUpgradeAnimation(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, startPaused: boolean = false): void { + globalScene.tweens.killTweensOf(icon); + // Skip animations if they are disabled + if (globalScene.candyUpgradeDisplay === 0 || species.speciesId !== species.getRootSpeciesId(false)) { + return; + } + + icon.y = 2; + + const tweenChain: Phaser.Types.Tweens.TweenChainBuilderConfig = { + targets: icon, + loop: -1, + // Make the initial bounce a little randomly delayed + delay: randIntRange(0, 50) * 5, + loopDelay: 1000, + tweens: [ + { + targets: icon, + y: 2 - 5, + duration: fixedInt(125), + ease: "Cubic.easeOut", + yoyo: true + }, + { + targets: icon, + y: 2 - 3, + duration: fixedInt(150), + ease: "Cubic.easeOut", + yoyo: true + } + ], }; + + const isPassiveAvailable = this.isPassiveAvailable(species.speciesId); + const isValueReductionAvailable = this.isValueReductionAvailable(species.speciesId); + const isSameSpeciesEggAvailable = this.isSameSpeciesEggAvailable(species.speciesId); + + // 'Passives Only' mode + if (globalScene.candyUpgradeNotification === 1) { + if (isPassiveAvailable) { + globalScene.tweens.chain(tweenChain).paused = startPaused; + } + // 'On' mode + } else if (globalScene.candyUpgradeNotification === 2) { + if (isPassiveAvailable || isValueReductionAvailable || isSameSpeciesEggAvailable) { + globalScene.tweens.chain(tweenChain).paused = startPaused; + } + } + } + + /** + * Sets the visibility of a Candy Upgrade Icon + */ + setUpgradeIcon(starter: PokedexMonContainer): void { + const species = starter.species; + const slotVisible = !!species?.speciesId; + + if (!species || globalScene.candyUpgradeNotification === 0 || species.speciesId !== species.getRootSpeciesId(false)) { + starter.candyUpgradeIcon.setVisible(false); + starter.candyUpgradeOverlayIcon.setVisible(false); + return; + } + + const isPassiveAvailable = this.isPassiveAvailable(species.speciesId); + const isValueReductionAvailable = this.isValueReductionAvailable(species.speciesId); + const isSameSpeciesEggAvailable = this.isSameSpeciesEggAvailable(species.speciesId); + + // 'Passive Only' mode + if (globalScene.candyUpgradeNotification === 1) { + starter.candyUpgradeIcon.setVisible(slotVisible && isPassiveAvailable); + starter.candyUpgradeOverlayIcon.setVisible(slotVisible && starter.candyUpgradeIcon.visible); + + // 'On' mode + } else if (globalScene.candyUpgradeNotification === 2) { + starter.candyUpgradeIcon.setVisible( + slotVisible && ( isPassiveAvailable || isValueReductionAvailable || isSameSpeciesEggAvailable )); + starter.candyUpgradeOverlayIcon.setVisible(slotVisible && starter.candyUpgradeIcon.visible); + } + } + + /** + * Update the display of candy upgrade icons or animations for the given PokedexMonContainer + * @param pokemonContainer the container for the Pokemon to update + */ + updateCandyUpgradeDisplay(pokemonContainer: PokedexMonContainer) { + if (this.isUpgradeIconEnabled() ) { + this.setUpgradeIcon(pokemonContainer); + } + if (this.isUpgradeAnimationEnabled()) { + this.setUpgradeAnimation(pokemonContainer.icon, this.lastSpecies, true); + } + } + + processInput(button: Button): boolean { + if (this.blockInput) { + return false; + } + + const maxColumns = 9; + const numberOfStarters = this.filteredPokemonContainers.length; + const numOfRows = Math.ceil(numberOfStarters / maxColumns); + const currentRow = Math.floor(this.cursor / maxColumns); + const onScreenFirstIndex = this.scrollCursor * maxColumns; // this is first index on the screen + + // TODO: use the above to let the cursor go to the correct position when switching back. + + const ui = this.getUi(); + + let success = false; + let error = false; + + if (button === Button.SUBMIT) { + error = true; + } else if (button === Button.CANCEL) { + if (this.filterMode && this.filterBar.openDropDown) { + // CANCEL with a filter menu open > close it + this.filterBar.toggleDropDown(this.filterBarCursor); + + // if there are possible pokemon go the first one of the list + if (numberOfStarters > 0) { + this.setFilterMode(false); + this.scrollCursor = 0; + this.updateScroll(); + this.setCursor(0); + } + success = true; + + } else if (this.filterTextMode && !(this.filterText.getValue(this.filterTextCursor) === this.filterText.defaultText)) { + this.filterText.resetSelection(this.filterTextCursor); + success = true; + } else { + this.tryExit(); + success = true; + } + } else if (button === Button.STATS) { + if (!this.filterMode) { + this.cursorObj.setVisible(false); + this.setSpecies(null); + this.filterText.cursorObj.setVisible(false); + this.filterTextMode = false; + this.filterBarCursor = 0; + this.setFilterMode(true); + } + } else if (button === Button.V) { + if (!this.filterTextMode) { + this.cursorObj.setVisible(false); + this.setSpecies(null); + this.filterBar.cursorObj.setVisible(false); + this.filterMode = false; + this.filterTextCursor = 0; + this.setFilterTextMode(true); + } + } else if (button === Button.CYCLE_SHINY) { + this.showDecorations = !this.showDecorations; + this.updateScroll(); + success = true; + } else if (this.filterMode) { + switch (button) { + case Button.LEFT: + if (this.filterBarCursor > 0) { + success = this.setCursor(this.filterBarCursor - 1); + } else { + success = this.setCursor(this.filterBar.numFilters - 1); + } + break; + case Button.RIGHT: + if (this.filterBarCursor < this.filterBar.numFilters - 1) { + success = this.setCursor(this.filterBarCursor + 1); + } else { + success = this.setCursor(0); + } + break; + case Button.UP: + if (this.filterBar.openDropDown) { + success = this.filterBar.decDropDownCursor(); + } else if (numberOfStarters > 0) { + // UP from filter bar to bottom of Pokemon list + this.setFilterMode(false); + this.scrollCursor = Math.max(0, numOfRows - 9); + this.updateScroll(); + const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1); + const targetCol = Math.min(8, proportion < 0.5 ? Math.floor(proportion * 8) : Math.ceil(proportion * 8)); + if (numberOfStarters % 9 > targetCol) { + this.setCursor(numberOfStarters - (numberOfStarters) % 9 + targetCol); + } else { + this.setCursor(Math.max(numberOfStarters - (numberOfStarters) % 9 + targetCol - 9, 0)); + } + success = true; + } + break; + case Button.DOWN: + if (this.filterBar.openDropDown) { + success = this.filterBar.incDropDownCursor(); + } else if (numberOfStarters > 0) { + // DOWN from filter bar to top of Pokemon list + this.setFilterMode(false); + this.scrollCursor = 0; + this.updateScroll(); + const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1); + const targetCol = Math.min(8, proportion < 0.5 ? Math.floor(proportion * 8) : Math.ceil(proportion * 8)); + this.setCursor(Math.min(targetCol, numberOfStarters)); + success = true; + } + break; + case Button.ACTION: + if (!this.filterBar.openDropDown) { + this.filterBar.toggleDropDown(this.filterBarCursor); + } else { + this.filterBar.toggleOptionState(); + } + success = true; + break; + } + } else if (this.filterTextMode) { + switch (button) { + case Button.LEFT: + // LEFT from filter bar, move to right of Pokemon list + if (numberOfStarters > 0) { + this.setFilterTextMode(false); + const rowIndex = this.filterTextCursor; + this.setCursor(onScreenFirstIndex + (rowIndex < numOfRows - 1 ? (rowIndex + 1) * maxColumns - 1 : numberOfStarters - 1)); + success = true; + } + break; + case Button.RIGHT: + // RIGHT from filter bar, move to left of Pokemon list + if (numberOfStarters > 0) { + this.setFilterTextMode(false); + const rowIndex = this.filterTextCursor; + this.setCursor(onScreenFirstIndex + (rowIndex < numOfRows ? rowIndex * maxColumns : (numOfRows - 1) * maxColumns)); + success = true; + } + break; + case Button.UP: + if (this.filterTextCursor > 0) { + success = this.setCursor(this.filterTextCursor - 1); + } else { + success = this.setCursor(this.filterText.numFilters - 1); + } + break; + case Button.DOWN: + if (this.filterTextCursor < this.filterText.numFilters - 1) { + success = this.setCursor(this.filterTextCursor + 1); + } else { + success = this.setCursor(0); + } + break; + case Button.ACTION: + this.filterText.startSearch(this.filterTextCursor, this.getUi()); + success = true; + break; + } + } else { + + if (button === Button.ACTION) { + ui.setOverlayMode(Mode.POKEDEX_PAGE, this.lastSpecies, 0); + success = true; + } else { + switch (button) { + case Button.UP: + if (currentRow > 0) { + if (this.scrollCursor > 0 && currentRow - this.scrollCursor === 0) { + this.scrollCursor--; + this.updateScroll(); + } + success = this.setCursor(this.cursor - 9); + } else { + this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredPokemonContainers[this.cursor]); + this.setFilterMode(true); + success = true; + } + break; + case Button.DOWN: + if (currentRow < numOfRows - 1) { // not last row + if (currentRow - this.scrollCursor === 8) { // last row of visible pokemon + this.scrollCursor++; + } + success = this.setCursor(this.cursor + 9); + this.updateScroll(); + } else if (numOfRows > 1) { + // DOWN from last row of pokemon > Wrap around to first row + this.scrollCursor = 0; + this.updateScroll(); + success = this.setCursor(this.cursor % 9); + } else { + // DOWN from single row of pokemon > Go to filters + this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredPokemonContainers[this.cursor]); + this.setFilterMode(true); + success = true; + } + break; + case Button.LEFT: + if (this.cursor % 9 !== 0) { + success = this.setCursor(this.cursor - 1); + } else { + // LEFT from filtered pokemon, on the left edge + this.filterTextCursor = this.filterText.getNearestFilter(this.filteredPokemonContainers[this.cursor]); + this.setFilterTextMode(true); + success = true; + } + break; + case Button.RIGHT: + // is not right edge + if (this.cursor % 9 < (currentRow < numOfRows - 1 ? 8 : (numberOfStarters - 1) % 9)) { + success = this.setCursor(this.cursor + 1); + } else { + // RIGHT from filtered pokemon, on the right edge + this.filterTextCursor = this.filterText.getNearestFilter(this.filteredPokemonContainers[this.cursor]); + this.setFilterTextMode(true); + success = true; + } + break; + } + } + } + + if (success) { + ui.playSelect(); + } else if (error) { + ui.playError(); + } + + return success || error; + } + + updateButtonIcon(iconSetting, gamepadType, iconElement, controlLabel): void { + let iconPath; + // touch controls cannot be rebound as is, and are just emulating a keyboard event. + // Additionally, since keyboard controls can be rebound (and will be displayed when they are), we need to have special handling for the touch controls + if (gamepadType === "touch") { + gamepadType = "keyboard"; + switch (iconSetting) { + case SettingKeyboard.Button_Cycle_Shiny: + iconPath = "R.png"; + break; + case SettingKeyboard.Button_Cycle_Variant: + iconPath = "V.png"; + break; + case SettingKeyboard.Button_Stats: + iconPath = "C.png"; + break; + default: + break; + } + } else { + iconPath = globalScene.inputController?.getIconForLatestInputRecorded(iconSetting); + } + iconElement.setTexture(gamepadType, iconPath); + iconElement.setVisible(true); + controlLabel.setVisible(true); + } + + updateFilterButtonIcon(iconSetting, gamepadType, iconElement, controlLabel): void { + let iconPath; + // touch controls cannot be rebound as is, and are just emulating a keyboard event. + // Additionally, since keyboard controls can be rebound (and will be displayed when they are), we need to have special handling for the touch controls + if (gamepadType === "touch") { + gamepadType = "keyboard"; + iconPath = "C.png"; + } else { + iconPath = globalScene.inputController?.getIconForLatestInputRecorded(iconSetting); + } + iconElement.setTexture(gamepadType, iconPath); + iconElement.setVisible(true); + controlLabel.setVisible(true); + } + + getSanitizedProps(props: DexAttrProps): DexAttrProps { + const sanitizedProps: DexAttrProps = { + shiny: false, + female: props.female, + variant: 0, + formIndex: 0, + }; + return sanitizedProps; + } + + // Returns true if one of the forms has the requested move + hasFormLevelMove(form: PokemonForm, selectedMove: string): boolean { + if (!pokemonFormLevelMoves.hasOwnProperty(form.speciesId) || !pokemonFormLevelMoves[form.speciesId].hasOwnProperty(form.formIndex)) { + return false; + } else { + const levelMoves = pokemonFormLevelMoves[form.speciesId][form.formIndex].map(m => allMoves[m[1]].name); + return levelMoves.includes(selectedMove); + } + } + + updateStarters = () => { + this.scrollCursor = 0; + this.filteredPokemonContainers = []; + this.validPokemonContainers = []; + + this.pokerusCursorObjs.forEach(cursor => cursor.setVisible(false)); + + this.filterBar.updateFilterLabels(); + this.filterText.updateFilterLabels(); + + this.validPokemonContainers = this.pokemonContainers; + + // this updates icons for previously saved pokemon + for (let i = 0; i < this.validPokemonContainers.length; i++) { + const currentFilteredContainer = this.validPokemonContainers[i]; + const starterSprite = currentFilteredContainer.icon as Phaser.GameObjects.Sprite; + + const currentDexAttr = this.getCurrentDexProps(currentFilteredContainer.species.speciesId); + const props = this.getSanitizedProps(globalScene.gameData.getSpeciesDexAttrProps(currentFilteredContainer.species, currentDexAttr)); + + starterSprite.setTexture(currentFilteredContainer.species.getIconAtlasKey(props.formIndex, props.shiny, props.variant), currentFilteredContainer.species.getIconId(props.female!, props.formIndex, props.shiny, props.variant)); + currentFilteredContainer.checkIconId(props.female, props.formIndex, props.shiny, props.variant); + } + + // filter + this.validPokemonContainers.forEach(container => { + container.setVisible(false); + + container.cost = globalScene.gameData.getSpeciesStarterValue(this.getStarterSpeciesId(container.species.speciesId)); + + // First, ensure you have the caught attributes for the species else default to bigint 0 + // TODO: This might be removed depending on how accessible we want the pokedex function to be + const caughtAttr = globalScene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0); + const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(container.species.speciesId)]; + const isStarterProgressable = speciesEggMoves.hasOwnProperty(this.getStarterSpeciesId(container.species.speciesId)); + + // Name filter + const selectedName = this.filterText.getValue(FilterTextRow.NAME); + const fitsName = container.species.name === selectedName || selectedName === this.filterText.defaultText; + + // Move filter + // TODO: There can be fringe cases where the two moves belong to mutually exclusive forms, these must be handled separately (Pikachu); + // On the other hand, in some cases it is possible to switch between different forms and combine (Deoxys) + const levelMoves = pokemonSpeciesLevelMoves[container.species.speciesId].map(m => allMoves[m[1]].name); + // This always gets egg moves from the starter + const eggMoves = speciesEggMoves[this.getStarterSpeciesId(container.species.speciesId)]?.map(m => allMoves[m].name) ?? []; + const tmMoves = speciesTmMoves[this.getStarterSpeciesId(container.species.speciesId)]?.map(m => allMoves[Array.isArray(m) ? m[1] : m].name) ?? []; + const selectedMove1 = this.filterText.getValue(FilterTextRow.MOVE_1); + const selectedMove2 = this.filterText.getValue(FilterTextRow.MOVE_2); + + const fitsFormMove1 = container.species.forms.some(form => this.hasFormLevelMove(form, selectedMove1)); + const fitsFormMove2 = container.species.forms.some(form => this.hasFormLevelMove(form, selectedMove2)); + const fitsLevelMove1 = levelMoves.includes(selectedMove1) || fitsFormMove1; + const fitsEggMove1 = eggMoves.includes(selectedMove1); + const fitsTmMove1 = tmMoves.includes(selectedMove1); + const fitsLevelMove2 = levelMoves.includes(selectedMove2) || fitsFormMove2; + const fitsEggMove2 = eggMoves.includes(selectedMove2); + const fitsTmMove2 = tmMoves.includes(selectedMove2); + const fitsMove1 = fitsLevelMove1 || fitsEggMove1 || fitsTmMove1 || selectedMove1 === this.filterText.defaultText; + const fitsMove2 = fitsLevelMove2 || fitsEggMove2 || fitsTmMove2 || selectedMove2 === this.filterText.defaultText; + const fitsMoves = fitsMove1 && fitsMove2; + + container.eggMove1Icon.setVisible(false); + container.tmMove1Icon.setVisible(false); + container.eggMove2Icon.setVisible(false); + container.tmMove2Icon.setVisible(false); + if (fitsEggMove1 && !fitsLevelMove1) { + container.eggMove1Icon.setVisible(true); + } else if (fitsTmMove1 && !fitsLevelMove1) { + container.tmMove1Icon.setVisible(true); + } + if (fitsEggMove2 && !fitsLevelMove2) { + container.eggMove2Icon.setVisible(true); + } else if (fitsTmMove2 && !fitsLevelMove2) { + container.tmMove2Icon.setVisible(true); + } + + // Ability filter + const abilities = [ container.species.ability1, container.species.ability2, container.species.abilityHidden ].map(a => allAbilities[a].name); + const passives = starterPassiveAbilities[this.getStarterSpeciesId(container.species.speciesId)] ?? {} as PassiveAbilities; + + const selectedAbility1 = this.filterText.getValue(FilterTextRow.ABILITY_1); + const fitsFormAbility = container.species.forms.some(form => allAbilities[form.ability1].name === selectedAbility1); + const fitsAbility1 = abilities.includes(selectedAbility1) || fitsFormAbility || selectedAbility1 === this.filterText.defaultText; + const fitsPassive1 = Object.values(passives).some(p => p.name === selectedAbility1); + + const selectedAbility2 = this.filterText.getValue(FilterTextRow.ABILITY_2); + const fitsAbility2 = abilities.includes(selectedAbility2) || fitsFormAbility || selectedAbility2 === this.filterText.defaultText; + const fitsPassive2 = Object.values(passives).some(p => p.name === selectedAbility2); + + // If both fields have been set to the same ability, show both ability and passive + const fitsAbilities = (fitsAbility1 && (fitsPassive2 || selectedAbility2 === this.filterText.defaultText)) || + (fitsAbility2 && (fitsPassive1 || selectedAbility1 === this.filterText.defaultText)); + + container.passive1Icon.setVisible(false); + container.passive2Icon.setVisible(false); + if (fitsPassive1) { + container.passive1Icon.setVisible(true); + } + if (fitsPassive2) { + container.passive2Icon.setVisible(true); + } + + // Gen filter + const fitsGen = this.filterBar.getVals(DropDownColumn.GEN).includes(container.species.generation); + + // Type filter + const fitsType = this.filterBar.getVals(DropDownColumn.TYPES).some(type => container.species.isOfType((type as number) - 1)); + + // Biome filter + const indexToBiome = new Map( + Object.values(Biome) + .map((value, index) => (typeof value === "string" ? [ index, value ] : undefined)) + .filter((entry): entry is [number, string] => entry !== undefined) + ); + indexToBiome.set(35, "Uncatchable"); + + // We get biomes for both the mon and its starters to ensure that evolutions get the correct filters. + // TODO: We might also need to do it the other way around. + const biomes = catchableSpecies[container.species.speciesId].concat(catchableSpecies[this.getStarterSpeciesId(container.species.speciesId)]).map(b => Biome[b.biome]); + if (biomes.length === 0) { + biomes.push("Uncatchable"); + } + const showNoBiome = (biomes.length === 0 && this.filterBar.getVals(DropDownColumn.BIOME).length === 36) ? true : false; + const fitsBiome = this.filterBar.getVals(DropDownColumn.BIOME).some(item => biomes.includes(indexToBiome.get(item) ?? "")) || showNoBiome; + + + // Caught / Shiny filter + const isNonShinyCaught = !!(caughtAttr & DexAttr.NON_SHINY); + const isShinyCaught = !!(caughtAttr & DexAttr.SHINY); + const isVariant1Caught = isShinyCaught && !!(caughtAttr & DexAttr.DEFAULT_VARIANT); + const isVariant2Caught = isShinyCaught && !!(caughtAttr & DexAttr.VARIANT_2); + const isVariant3Caught = isShinyCaught && !!(caughtAttr & DexAttr.VARIANT_3); + const isUncaught = !isNonShinyCaught && !isVariant1Caught && !isVariant2Caught && !isVariant3Caught; + const fitsCaught = this.filterBar.getVals(DropDownColumn.CAUGHT).some(caught => { + if (caught === "SHINY3") { + return isVariant3Caught; + } else if (caught === "SHINY2") { + return isVariant2Caught && !isVariant3Caught; + } else if (caught === "SHINY") { + return isVariant1Caught && !isVariant2Caught && !isVariant3Caught; + } else if (caught === "NORMAL") { + return isNonShinyCaught && !isVariant1Caught && !isVariant2Caught && !isVariant3Caught; + } else if (caught === "UNCAUGHT") { + return isUncaught; + } + }); + + // Passive Filter + const isPassiveUnlocked = starterData.passiveAttr > 0; + const isPassiveUnlockable = this.isPassiveAvailable(container.species.speciesId) && !isPassiveUnlocked; + const fitsPassive = this.filterBar.getVals(DropDownColumn.UNLOCKS).some(unlocks => { + if (unlocks.val === "PASSIVE" && unlocks.state === DropDownState.ON) { + return isPassiveUnlocked; + } else if (unlocks.val === "PASSIVE" && unlocks.state === DropDownState.EXCLUDE) { + return isStarterProgressable && !isPassiveUnlocked; + } else if (unlocks.val === "PASSIVE" && unlocks.state === DropDownState.UNLOCKABLE) { + return isPassiveUnlockable; + } else if (unlocks.val === "PASSIVE" && unlocks.state === DropDownState.OFF) { + return true; + } + }); + + // Cost Reduction Filter + const isCostReducedByOne = starterData.valueReduction === 1; + const isCostReducedByTwo = starterData.valueReduction === 2; + const isCostReductionUnlockable = this.isValueReductionAvailable(container.species.speciesId); + const fitsCostReduction = this.filterBar.getVals(DropDownColumn.UNLOCKS).some(unlocks => { + if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.ON) { + return isCostReducedByOne || isCostReducedByTwo; + } else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.ONE) { + return isCostReducedByOne; + } else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.TWO) { + return isCostReducedByTwo; + } else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.EXCLUDE) { + return isStarterProgressable && !(isCostReducedByOne || isCostReducedByTwo); + } else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.UNLOCKABLE) { + return isCostReductionUnlockable; + } else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.OFF) { + return true; + } + }); + + // Starter Filter + const isStarter = this.getStarterSpeciesId(container.species.speciesId) === container.species.speciesId; + const fitsStarter = this.filterBar.getVals(DropDownColumn.MISC).some(misc => { + if (misc.val === "STARTER" && misc.state === DropDownState.ON) { + return isStarter; + } + if (misc.val === "STARTER" && misc.state === DropDownState.EXCLUDE) { + return !isStarter; + } + if (misc.val === "STARTER" && misc.state === DropDownState.OFF) { + return true; + } + }); + + // Favorite Filter + const isFavorite = this.starterPreferences[container.species.speciesId]?.favorite ?? false; + const fitsFavorite = this.filterBar.getVals(DropDownColumn.MISC).some(misc => { + if (misc.val === "FAVORITE" && misc.state === DropDownState.ON) { + return isFavorite; + } + if (misc.val === "FAVORITE" && misc.state === DropDownState.EXCLUDE) { + return !isFavorite; + } + if (misc.val === "FAVORITE" && misc.state === DropDownState.OFF) { + return true; + } + }); + + // Ribbon / Classic Win Filter + const hasWon = starterData.classicWinCount > 0; + const hasNotWon = starterData.classicWinCount === 0; + const isUndefined = starterData.classicWinCount === undefined; + const fitsWin = this.filterBar.getVals(DropDownColumn.MISC).some(misc => { + if (misc.val === "WIN" && misc.state === DropDownState.ON) { + return hasWon; + } else if (misc.val === "WIN" && misc.state === DropDownState.EXCLUDE) { + return hasNotWon || isUndefined; + } else if (misc.val === "WIN" && misc.state === DropDownState.OFF) { + return true; + } + }); + + // HA Filter + const speciesHasHiddenAbility = container.species.abilityHidden !== container.species.ability1 && container.species.abilityHidden !== Abilities.NONE; + const hasHA = starterData.abilityAttr & AbilityAttr.ABILITY_HIDDEN; + const fitsHA = this.filterBar.getVals(DropDownColumn.MISC).some(misc => { + if (misc.val === "HIDDEN_ABILITY" && misc.state === DropDownState.ON) { + return hasHA; + } else if (misc.val === "HIDDEN_ABILITY" && misc.state === DropDownState.EXCLUDE) { + return speciesHasHiddenAbility && !hasHA; + } else if (misc.val === "HIDDEN_ABILITY" && misc.state === DropDownState.OFF) { + return true; + } + }); + + // Egg Purchasable Filter + const isEggPurchasable = this.isSameSpeciesEggAvailable(container.species.speciesId); + const fitsEgg = this.filterBar.getVals(DropDownColumn.MISC).some(misc => { + if (misc.val === "EGG" && misc.state === DropDownState.ON) { + return isEggPurchasable; + } else if (misc.val === "EGG" && misc.state === DropDownState.EXCLUDE) { + return isStarterProgressable && !isEggPurchasable; + } else if (misc.val === "EGG" && misc.state === DropDownState.OFF) { + return true; + } + }); + + // Pokerus Filter + const fitsPokerus = this.filterBar.getVals(DropDownColumn.MISC).some(misc => { + if (misc.val === "POKERUS" && misc.state === DropDownState.ON) { + return this.pokerusSpecies.includes(container.species); + } else if (misc.val === "POKERUS" && misc.state === DropDownState.EXCLUDE) { + return !this.pokerusSpecies.includes(container.species); + } else if (misc.val === "POKERUS" && misc.state === DropDownState.OFF) { + return true; + } + }); + + if (fitsName && fitsAbilities && fitsMoves && fitsGen && fitsBiome && fitsType && fitsCaught && fitsPassive && fitsCostReduction && fitsStarter && fitsFavorite && fitsWin && fitsHA && fitsEgg && fitsPokerus) { + this.filteredPokemonContainers.push(container); + } + }); + + this.starterSelectScrollBar.setTotalRows(Math.max(Math.ceil(this.filteredPokemonContainers.length / 9), 1)); + this.starterSelectScrollBar.setScrollCursor(0); + + // sort + const sort = this.filterBar.getVals(DropDownColumn.SORT)[0]; + this.filteredPokemonContainers.sort((a, b) => { + switch (sort.val) { + default: + break; + case SortCriteria.NUMBER: + return (a.species.speciesId - b.species.speciesId) * -sort.dir; + case SortCriteria.COST: + return (a.cost - b.cost) * -sort.dir; + case SortCriteria.CANDY: + const candyCountA = globalScene.gameData.starterData[a.species.speciesId].candyCount; + const candyCountB = globalScene.gameData.starterData[b.species.speciesId].candyCount; + return (candyCountA - candyCountB) * -sort.dir; + case SortCriteria.IV: + const avgIVsA = globalScene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0) / globalScene.gameData.dexData[a.species.speciesId].ivs.length; + const avgIVsB = globalScene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0) / globalScene.gameData.dexData[b.species.speciesId].ivs.length; + return (avgIVsA - avgIVsB) * -sort.dir; + case SortCriteria.NAME: + return a.species.name.localeCompare(b.species.name) * -sort.dir; + case SortCriteria.CAUGHT: + return (globalScene.gameData.dexData[a.species.speciesId].caughtCount - globalScene.gameData.dexData[b.species.speciesId].caughtCount) * -sort.dir; + case SortCriteria.HATCHED: + return (globalScene.gameData.dexData[this.getStarterSpeciesId(a.species.speciesId)].hatchedCount - globalScene.gameData.dexData[this.getStarterSpeciesId(b.species.speciesId)].hatchedCount) * -sort.dir; + } + return 0; + }); + + this.updateScroll(); + }; + + updateScroll = () => { + const maxColumns = 9; + const maxRows = 9; + const onScreenFirstIndex = this.scrollCursor * maxColumns; + const onScreenLastIndex = Math.min(this.filteredPokemonContainers.length - 1, onScreenFirstIndex + maxRows * maxColumns - 1); + + this.starterSelectScrollBar.setScrollCursor(this.scrollCursor); + + this.pokerusCursorObjs.forEach(cursorObj => cursorObj.setVisible(false)); + + let pokerusCursorIndex = 0; + this.filteredPokemonContainers.forEach((container, i) => { + const pos = calcStarterPosition(i, this.scrollCursor); + container.setPosition(pos.x, pos.y); + if (i < onScreenFirstIndex || i > onScreenLastIndex) { + container.setVisible(false); + return; + } else { + container.setVisible(true); + + if (this.showDecorations) { + + if (this.pokerusSpecies.includes(container.species)) { + this.pokerusCursorObjs[pokerusCursorIndex].setPosition(pos.x - 1, pos.y + 1); + this.pokerusCursorObjs[pokerusCursorIndex].setVisible(true); + pokerusCursorIndex++; + } + + const speciesId = container.species.speciesId; + this.updateStarterValueLabel(container); + + container.label.setVisible(true); + const speciesVariants = speciesId && globalScene.gameData.dexData[speciesId].caughtAttr & DexAttr.SHINY + ? [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => !!(globalScene.gameData.dexData[speciesId].caughtAttr & v)) + : []; + for (let v = 0; v < 3; v++) { + const hasVariant = speciesVariants.length > v; + container.shinyIcons[v].setVisible(hasVariant); + if (hasVariant) { + container.shinyIcons[v].setTint(getVariantTint(speciesVariants[v] === DexAttr.DEFAULT_VARIANT ? 0 : speciesVariants[v] === DexAttr.VARIANT_2 ? 1 : 2)); + } + } + + container.starterPassiveBgs.setVisible(!!globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].passiveAttr); + container.hiddenAbilityIcon.setVisible(!!globalScene.gameData.dexData[speciesId].caughtAttr && !!(globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].abilityAttr & 4)); + container.classicWinIcon.setVisible(globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].classicWinCount > 0); + container.favoriteIcon.setVisible(this.starterPreferences[speciesId]?.favorite ?? false); + + // 'Candy Icon' mode + if (globalScene.candyUpgradeDisplay === 0) { + + if (!starterColors[this.getStarterSpeciesId(speciesId)]) { + // Default to white if no colors are found + starterColors[this.getStarterSpeciesId(speciesId)] = [ "ffffff", "ffffff" ]; + } + + // Set the candy colors + container.candyUpgradeIcon.setTint(argbFromRgba(rgbHexToRgba(starterColors[this.getStarterSpeciesId(speciesId)][0]))); + container.candyUpgradeOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(starterColors[this.getStarterSpeciesId(speciesId)][1]))); + + } else if (globalScene.candyUpgradeDisplay === 1) { + container.candyUpgradeIcon.setVisible(false); + container.candyUpgradeOverlayIcon.setVisible(false); + } + } else { + container.label.setVisible(false); + for (let v = 0; v < 3; v++) { + container.shinyIcons[v].setVisible(false); + } + container.starterPassiveBgs.setVisible(false); + container.hiddenAbilityIcon.setVisible(false); + container.classicWinIcon.setVisible(false); + container.favoriteIcon.setVisible(false); + + container.candyUpgradeIcon.setVisible(false); + container.candyUpgradeOverlayIcon.setVisible(false); + } + } + }); + }; + + setCursor(cursor: number): boolean { + let changed = false; + + if (this.filterMode) { + changed = this.filterBarCursor !== cursor; + this.filterBarCursor = cursor; + this.filterBar.setCursor(cursor); + } else if (this.filterTextMode) { + changed = this.filterTextCursor !== cursor; + this.filterTextCursor = cursor; + this.filterText.setCursor(cursor); + } else { + cursor = Math.max(Math.min(this.filteredPokemonContainers.length - 1, cursor), 0); + changed = super.setCursor(cursor); + + const pos = calcStarterPosition(cursor, this.scrollCursor); + this.cursorObj.setPosition(pos.x - 1, pos.y + 1); + + const species = this.filteredPokemonContainers[cursor]?.species; + + if (species) { + this.setSpecies(species); + } + } + + return changed; + } + + setFilterMode(filterMode: boolean): boolean { + this.cursorObj.setVisible(!filterMode); + this.filterBar.cursorObj.setVisible(filterMode); + this.pokemonSprite.setVisible(false); + + if (filterMode !== this.filterMode) { + this.filterMode = filterMode; + this.setCursor(filterMode ? this.filterBarCursor : this.cursor); + if (filterMode) { + this.setSpecies(null); + } + return true; + } + return false; + } + + setFilterTextMode(filterTextMode: boolean): boolean { + this.cursorObj.setVisible(!filterTextMode); + this.filterText.cursorObj.setVisible(filterTextMode); + this.pokemonSprite.setVisible(false); + + if (filterTextMode !== this.filterTextMode) { + this.filterTextMode = filterTextMode; + this.setCursor(filterTextMode ? this.filterTextCursor : this.cursor); + if (filterTextMode) { + this.setSpecies(null); + } + return true; + } + return false; + } + + getFriendship(speciesId: number) { + let currentFriendship = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].friendship; + if (!currentFriendship || currentFriendship === undefined) { + currentFriendship = 0; + } + + const friendshipCap = getStarterValueFriendshipCap(speciesStarterCosts[speciesId]); + + return { currentFriendship, friendshipCap }; + } + + setSpecies(species: PokemonSpecies | null) { + + this.speciesStarterDexEntry = species ? globalScene.gameData.dexData[species.speciesId] : null; + + if (!species && globalScene.ui.getTooltip().visible) { + globalScene.ui.hideTooltip(); + } + + if (this.lastSpecies) { + const dexAttr = this.getCurrentDexProps(this.lastSpecies.speciesId); + const props = this.getSanitizedProps(globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, dexAttr)); + const speciesIndex = this.allSpecies.indexOf(this.lastSpecies); + const lastSpeciesIcon = this.pokemonContainers[speciesIndex].icon; + this.checkIconId(lastSpeciesIcon, this.lastSpecies, props.female, props.formIndex, props.shiny, props.variant); + this.iconAnimHandler.addOrUpdate(lastSpeciesIcon, PokemonIconAnimMode.NONE); + + // Resume the animation for the previously selected species + const icon = this.pokemonContainers[speciesIndex].icon; + globalScene.tweens.getTweensOf(icon).forEach(tween => tween.resume()); + } + + this.lastSpecies = species!; // TODO: is this bang correct? + + if (species && (this.speciesStarterDexEntry?.seenAttr || this.speciesStarterDexEntry?.caughtAttr)) { + + this.pokemonNumberText.setText(i18next.t("pokedexUiHandler:pokemonNumber") + padInt(species.speciesId, 4)); + + this.pokemonNameText.setText(species.name); + + if (this.speciesStarterDexEntry?.caughtAttr) { + + // Pause the animation when the species is selected + const speciesIndex = this.allSpecies.indexOf(species); + const icon = this.pokemonContainers[speciesIndex].icon; + + if (this.isUpgradeAnimationEnabled()) { + globalScene.tweens.getTweensOf(icon).forEach(tween => tween.pause()); + // Reset the position of the icon + icon.x = -2; + icon.y = 2; + } + + // Initiates the small up and down idle animation + this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.PASSIVE); + + const speciesForm = getPokemonSpeciesForm(species.speciesId, 0); + this.setTypeIcons(speciesForm.type1, speciesForm.type2); + + this.setSpeciesDetails(species, {}); + + this.pokemonSprite.clearTint(); + + this.type1Icon.clearTint(); + this.type2Icon.clearTint(); + } else { + this.type1Icon.setVisible(true); + this.type2Icon.setVisible(true); + + this.setSpeciesDetails(species, { + forSeen: true + }); + this.pokemonSprite.setTint(0x808080); + } + } else { + this.pokemonNumberText.setText(species ? i18next.t("pokedexUiHandler:pokemonNumber") + padInt(species.speciesId, 4) : ""); + this.pokemonNameText.setText(species ? "???" : ""); + this.type1Icon.setVisible(false); + this.type2Icon.setVisible(false); + if (species) { + this.pokemonSprite.setTint(0x000000); + this.setSpeciesDetails(species, {}); + } + } + } + + setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}): void { + let { shiny, formIndex, female, variant } = options; + const forSeen: boolean = options.forSeen ?? false; + + // We will only update the sprite if there is a change to form, shiny/variant + // or gender for species with gender sprite differences + const shouldUpdateSprite = true; + + if (species?.forms?.find(f => f.formKey === "female")) { + if (female !== undefined) { + formIndex = female ? 1 : 0; + } else if (formIndex !== undefined) { + female = formIndex === 1; + } + } + + this.pokemonSprite.setVisible(false); + + if (this.assetLoadCancelled) { + this.assetLoadCancelled.value = true; + this.assetLoadCancelled = null; + } + + this.starterMoveset = null; + this.speciesStarterMoves = []; + + if (species) { + const dexEntry = globalScene.gameData.dexData[species.speciesId]; + + if (!dexEntry.caughtAttr) { + const props = this.getSanitizedProps(globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId))); + + if (shiny === undefined || shiny !== props.shiny) { + shiny = props.shiny; + } + if (formIndex === undefined || formIndex !== props.formIndex) { + formIndex = props.formIndex; + } + if (female === undefined || female !== props.female) { + female = props.female; + } + if (variant === undefined || variant !== props.variant) { + variant = props.variant; + } + } + + const assetLoadCancelled = new BooleanHolder(false); + this.assetLoadCancelled = assetLoadCancelled; + + if (shouldUpdateSprite) { + + species.loadAssets(female!, formIndex, shiny, variant, true).then(() => { // TODO: is this bang correct? + if (assetLoadCancelled.value) { + return; + } + this.assetLoadCancelled = null; + this.speciesLoaded.set(species.speciesId, true); + this.pokemonSprite.play(species.getSpriteKey(female!, formIndex, shiny, variant)); // TODO: is this bang correct? + this.pokemonSprite.setPipelineData("shiny", shiny); + this.pokemonSprite.setPipelineData("variant", variant); + this.pokemonSprite.setPipelineData("spriteKey", species.getSpriteKey(female!, formIndex, shiny, variant)); // TODO: is this bang correct? + this.pokemonSprite.setVisible(true); + }); + } else { + this.pokemonSprite.setVisible(!(this.filterMode || this.filterTextMode)); + } + + if (dexEntry.caughtAttr || forSeen) { + + const speciesForm = getPokemonSpeciesForm(species.speciesId, 0); // TODO: always selecting the first form + + this.setTypeIcons(speciesForm.type1, speciesForm.type2); + } else { + this.setTypeIcons(null, null); + } + } else { + this.setTypeIcons(null, null); + } + + if (!this.starterMoveset) { + this.starterMoveset = this.speciesStarterMoves.slice(0, 4) as StarterMoveset; + } + } + + setTypeIcons(type1: Type | null, type2: Type | null): void { + if (type1 !== null) { + this.type1Icon.setVisible(true); + this.type1Icon.setFrame(Type[type1].toLowerCase()); + } else { + this.type1Icon.setVisible(false); + } + if (type2 !== null) { + this.type2Icon.setVisible(true); + this.type2Icon.setFrame(Type[type2].toLowerCase()); + } else { + this.type2Icon.setVisible(false); + } + } + + updateStarterValueLabel(starter: PokedexMonContainer): void { + const speciesId = starter.species.speciesId; + const baseStarterValue = speciesStarterCosts[speciesId]; + const starterValue = globalScene.gameData.getSpeciesStarterValue(this.getStarterSpeciesId(speciesId)); + starter.cost = starterValue; + let valueStr = starterValue.toString(); + if (valueStr.startsWith("0.")) { + valueStr = valueStr.slice(1); + } + starter.label.setText(valueStr); + let textStyle: TextStyle; + switch (baseStarterValue - starterValue) { + case 0: + textStyle = TextStyle.WINDOW; + break; + case 1: + case 0.5: + textStyle = TextStyle.SUMMARY_BLUE; + break; + default: + textStyle = TextStyle.SUMMARY_GOLD; + break; + } + if (baseStarterValue - starterValue > 0) { + starter.label.setColor(this.getTextColor(textStyle)); + starter.label.setShadowColor(this.getTextColor(textStyle, true)); + } + } + + tryExit(): boolean { + this.blockInput = true; + const ui = this.getUi(); + + const cancel = () => { + ui.setMode(Mode.POKEDEX, "refresh"); + this.clearText(); + this.blockInput = false; + }; + ui.showText(i18next.t("pokedexUiHandler:confirmExit"), null, () => { + ui.setModeWithoutClear(Mode.CONFIRM, () => { + ui.setMode(Mode.POKEDEX, "refresh"); + globalScene.clearPhaseQueue(); + this.clearText(); + this.clear(); + ui.revertMode(); + }, cancel, null, null, 19); + }); + + return true; + } + + + /** + * Creates a temporary dex attr props that will be used to + * display the correct shiny, variant, and form based on the StarterPreferences + * + * @param speciesId the id of the species to get props for + * @returns the dex props + */ + getCurrentDexProps(speciesId: number): bigint { + let props = 0n; + const caughtAttr = globalScene.gameData.dexData[speciesId].caughtAttr; + + /* this checks the gender of the pokemon; this works by checking a) that the starter preferences for the species exist, and if so, is it female. If so, it'll add DexAttr.FEMALE to our temp props + * It then checks b) if the caughtAttr for the pokemon is female and NOT male - this means that the ONLY gender we've gotten is female, and we need to add DexAttr.FEMALE to our temp props + * If neither of these pass, we add DexAttr.MALE to our temp props + */ + if (this.starterPreferences[speciesId]?.female || ((caughtAttr & DexAttr.FEMALE) > 0n && (caughtAttr & DexAttr.MALE) === 0n)) { + props += DexAttr.FEMALE; + } else { + props += DexAttr.MALE; + } + /* This part is very similar to above, but instead of for gender, it checks for shiny within starter preferences. + * If they're not there, it enables shiny state by default if any shiny was caught + */ + if (this.starterPreferences[speciesId]?.shiny || ((caughtAttr & DexAttr.SHINY) > 0n && this.starterPreferences[speciesId]?.shiny !== false)) { + props += DexAttr.SHINY; + if (this.starterPreferences[speciesId]?.variant !== undefined) { + props += BigInt(Math.pow(2, this.starterPreferences[speciesId]?.variant)) * DexAttr.DEFAULT_VARIANT; + } else { + /* This calculates the correct variant if there's no starter preferences for it. + * This gets the highest tier variant that you've caught and adds it to the temp props + */ + if ((caughtAttr & DexAttr.VARIANT_3) > 0) { + props += DexAttr.VARIANT_3; + } else if ((caughtAttr & DexAttr.VARIANT_2) > 0) { + props += DexAttr.VARIANT_2; + } else { + props += DexAttr.DEFAULT_VARIANT; + } + } + } else { + props += DexAttr.NON_SHINY; + props += DexAttr.DEFAULT_VARIANT; // we add the default variant here because non shiny versions are listed as default variant + } + if (this.starterPreferences[speciesId]?.form) { // this checks for the form of the pokemon + props += BigInt(Math.pow(2, this.starterPreferences[speciesId]?.form)) * DexAttr.DEFAULT_FORM; + } else { + // Get the first unlocked form + props += globalScene.gameData.getFormAttr(globalScene.gameData.getFormIndex(caughtAttr)); + } + + return props; + } + + override destroy(): void { + this.pokemonContainers = []; + } + + clearText() { + this.starterSelectMessageBoxContainer.setVisible(false); + super.clearText(); + } + + clear(): void { + super.clear(); + + this.cursor = -1; + globalScene.ui.hideTooltip(); + + this.starterSelectContainer.setVisible(false); + this.blockInput = false; + } + + checkIconId(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, female: boolean, formIndex: number, shiny: boolean, variant: number) { + if (icon.frame.name !== species.getIconId(female, formIndex, shiny, variant)) { + console.log(`${species.name}'s icon ${icon.frame.name} does not match getIconId with female: ${female}, formIndex: ${formIndex}, shiny: ${shiny}, variant: ${variant}`); + icon.setTexture(species.getIconAtlasKey(formIndex, false, variant)); + icon.setFrame(species.getIconId(female, formIndex, false, variant)); + } + } +} diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 938779dfee6..20ca2cc88da 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1970,6 +1970,21 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } }); } + options.push({ + label: i18next.t("menuUiHandler:POKEDEX"), + handler: () => { + ui.setMode(Mode.STARTER_SELECT).then(() => { + const attributes = { + shiny: starterAttributes.shiny, + variant: starterAttributes.variant, + form: starterAttributes.form, + female: starterAttributes.female + }; + ui.setOverlayMode(Mode.POKEDEX_PAGE, this.lastSpecies, starterAttributes.form, attributes); + return true; + }); + } + }); options.push({ label: i18next.t("menu:cancel"), handler: () => { diff --git a/src/ui/text.ts b/src/ui/text.ts index fc48c4ea86e..19b0eddb494 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -42,6 +42,7 @@ export enum TextStyle { PERFECT_IV, ME_OPTION_DEFAULT, // Default style for choices in ME ME_OPTION_SPECIAL, // Style for choices with special requirements in ME + SHADOW_TEXT // To obscure unavailable options } export interface TextStyleOptions { @@ -359,6 +360,12 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui return !shadow ? "#f8b050" : "#c07800"; // Gold } return !shadow ? "#78c850" : "#306850"; // Green + // Leaving the logic in place, in case someone wants to pick an even darker hue for the shadow down the line + case TextStyle.SHADOW_TEXT: + if (isLegacyTheme) { + return !shadow ? "#d0d0c8" : "#d0d0c8"; + } + return !shadow ? "#6b5a73" : "#6b5a73"; } } diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index 538f78e877e..0d69eae0efc 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -1,7 +1,7 @@ import OptionSelectUiHandler from "./settings/option-select-ui-handler"; import { Mode } from "./ui"; import * as Utils from "../utils"; -import { TextStyle, addTextObject, getTextStyleOptions } from "./text"; +import { TextStyle, addTextObject } from "./text"; import { getSplashMessages } from "../data/splash-messages"; import i18next from "i18next"; import { TimedEventDisplay } from "#app/timed-event-manager"; @@ -47,8 +47,8 @@ export default class TitleUiHandler extends OptionSelectUiHandler { } this.playerCountLabel = addTextObject( - (globalScene.game.canvas.width / 6) - 2, - (globalScene.game.canvas.height / 6) - 13 - 576 * getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale, + // Actual y position will be determined after the title menu has been populated with options + (globalScene.game.canvas.width / 6) - 2, 0, `? ${i18next.t("menu:playersOnline")}`, TextStyle.MESSAGE, { fontSize: "54px" } @@ -96,6 +96,9 @@ export default class TitleUiHandler extends OptionSelectUiHandler { const ret = super.show(args); if (ret) { + // Moving player count to top of the menu + this.playerCountLabel.setY((globalScene.game.canvas.height / 6) - 13 - this.getWindowHeight()); + this.splashMessage = Utils.randItem(getSplashMessages()); this.splashMessageText.setText(i18next.t(this.splashMessage, { count: TitleUiHandler.BATTLES_WON_FALLBACK })); diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 72147e8ccb1..7fbd10b4668 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -23,6 +23,7 @@ import OptionSelectUiHandler from "./settings/option-select-ui-handler"; import EggHatchSceneHandler from "./egg-hatch-scene-handler"; import EggListUiHandler from "./egg-list-ui-handler"; import EggGachaUiHandler from "./egg-gacha-ui-handler"; +import PokedexUiHandler from "./pokedex-ui-handler"; import { addWindow } from "./ui-theme"; import LoginFormUiHandler from "./login-form-ui-handler"; import RegistrationFormUiHandler from "./registration-form-ui-handler"; @@ -53,6 +54,8 @@ import TestDialogueUiHandler from "#app/ui/test-dialogue-ui-handler"; import AutoCompleteUiHandler from "./autocomplete-ui-handler"; import { Device } from "#enums/devices"; import MysteryEncounterUiHandler from "./mystery-encounter-ui-handler"; +import PokedexScanUiHandler from "./pokedex-scan-ui-handler"; +import PokedexPageUiHandler from "./pokedex-page-ui-handler"; import { NavigationManager } from "./settings/navigationMenu"; export enum Mode { @@ -85,6 +88,9 @@ export enum Mode { GAME_STATS, EGG_LIST, EGG_GACHA, + POKEDEX, + POKEDEX_SCAN, + POKEDEX_PAGE, LOGIN_FORM, REGISTRATION_FORM, LOADING, @@ -109,6 +115,8 @@ const transitionModes = [ Mode.EGG_HATCH_SCENE, Mode.EGG_LIST, Mode.EGG_GACHA, + Mode.POKEDEX, + Mode.POKEDEX_PAGE, Mode.CHALLENGE_SELECT, Mode.RUN_HISTORY, ]; @@ -128,6 +136,7 @@ const noTransitionModes = [ Mode.SETTINGS_KEYBOARD, Mode.ACHIEVEMENTS, Mode.GAME_STATS, + Mode.POKEDEX_SCAN, Mode.LOGIN_FORM, Mode.REGISTRATION_FORM, Mode.LOADING, @@ -193,6 +202,9 @@ export default class UI extends Phaser.GameObjects.Container { new GameStatsUiHandler(), new EggListUiHandler(), new EggGachaUiHandler(), + new PokedexUiHandler(), + new PokedexScanUiHandler(Mode.TEST_DIALOGUE), + new PokedexPageUiHandler(), new LoginFormUiHandler(), new RegistrationFormUiHandler(), new LoadingModalUiHandler(), @@ -563,6 +575,7 @@ export default class UI extends Phaser.GameObjects.Container { revertMode(): Promise { return new Promise(resolve => { + if (!this?.modeChain?.length) { return resolve(false); }