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/CREDITS.md b/CREDITS.md index fd9a3d7bde3..099d410417e 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -343,34 +343,39 @@ In addition to the lists below, please check [the PokéRogue wiki](https://wiki. # 💻 Development -## Server Owner/Administrator +## Server Developers - pancakes aka patapancakes -## Senior Developers -- Walker -- NightKev -- Moka -- Temp aka Tempo-anon -- Madmadness65 - -## Developers +## Current and former Development Team members +- bennybroseph +- Brain Frog - CodeTappert +- Dakurei - flx-sta -- innerthunder - frutescens +- Greenlamp +- ImperialSympathizer +- innerthunder +- KimJeongSun +- Madmadness65 +- Moka +- Navori +- NightKev - Opaquer +- OrangeRed +- Sam aka Flashfyre (initial developer, started PokéRogue) +- sirzento - SN34KZ - Swain aka torranx - -## Junior Developers -- KimJeongSun -- ImperialSympathizer +- Temp aka Tempo-anon +- Walker +- Xavion ## Bug/Issue Managers -- Snailman - Daleks - Lily - PigeonBar +- Snailman ## Other Code Contributors - Admiral-Billy @@ -378,10 +383,7 @@ In addition to the lists below, please check [the PokéRogue wiki](https://wiki. - arColm - Arxalc - AsdarDevelops -- bennybroseph -- Brain Frog - Corrade -- Dakurei - DustinLin - ElizaAlex - EmberCM @@ -391,7 +393,6 @@ In addition to the lists below, please check [the PokéRogue wiki](https://wiki. - francktrouillez - FredeX - geeilhan -- Greenlamp - happinyz - hayuna - InfernoVulpix @@ -411,7 +412,6 @@ In addition to the lists below, please check [the PokéRogue wiki](https://wiki. - Neverblade - NxKarim - okimin -- OrangeRed - PigeonBar - PrabbyDD - prateau @@ -421,10 +421,8 @@ In addition to the lists below, please check [the PokéRogue wiki](https://wiki. - RedstonewolfX - ReneGV - rnicar245 -- Sam aka Flashfyre (initial developer, started PokéRogue) - schmidtc1 - shayebeadling -- sirzento - snoozbuster - sodaMelon - td76099 diff --git a/docs/enemy-ai.md b/docs/enemy-ai.md index 46482f56a90..8edf5a3f10e 100644 --- a/docs/enemy-ai.md +++ b/docs/enemy-ai.md @@ -80,8 +80,8 @@ As part of the move selection process, the enemy Pokémon must compute a **targe A move's UBS and TBS are computed with the respective functions in the `Move` class: ```ts -getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer; -getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer; +getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number; +getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number; ``` Logically, these functions are very similar – they add up their respective benefit scores from each of the move's attributes (as determined by `attr.getUserBenefitScore`, and `attr.getTargetBenefitScore`, respectively) and return the total benefit score. However, there are two key functional differences in how the UBS and TBS of a move are handled: diff --git a/eslint.config.js b/eslint.config.js index 1cea5563a78..0da9cc604bf 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,6 +5,7 @@ import importX from 'eslint-plugin-import-x'; export default [ { + name: "eslint-config", files: ["src/**/*.{ts,tsx,js,jsx}"], ignores: ["dist/*", "build/*", "coverage/*", "public/*", ".github/*", "node_modules/*", ".vscode/*"], languageOptions: { @@ -48,5 +49,22 @@ export default [ "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], // Disallows multiple empty lines "@typescript-eslint/consistent-type-imports": "error", // Enforces type-only imports wherever possible } + }, + { + name: "eslint-tests", + files: ["src/test/**/**.test.ts"], + languageOptions: { + parser: parser, + parserOptions: { + "project": ["./tsconfig.json"] + } + }, + plugins: { + "@typescript-eslint": tseslint + }, + rules: { + "@typescript-eslint/no-floating-promises": "error", // Require Promise-like statements to be handled appropriately. - https://typescript-eslint.io/rules/no-floating-promises/ + "@typescript-eslint/no-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/ + } } ] 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 da5bc393c52..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": { @@ -64,11 +64,5 @@ }, "engines": { "node": ">=20.0.0" - }, - "imports": { - "#enums/*": "./enums/*", - "#app": "./src/main.js", - "#app/*": "./src/*", - "#test/*": "./src/test/*" } } 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/fonts/pokemon-emerald-pro.ttf b/public/fonts/pokemon-emerald-pro.ttf index 758130172c7..d63ff2d6470 100644 Binary files a/public/fonts/pokemon-emerald-pro.ttf and b/public/fonts/pokemon-emerald-pro.ttf differ diff --git a/public/images/events/valentines2025event-de.png b/public/images/events/valentines2025event-de.png new file mode 100644 index 00000000000..0ec3bfe704b Binary files /dev/null and b/public/images/events/valentines2025event-de.png differ diff --git a/public/images/events/valentines2025event-en.png b/public/images/events/valentines2025event-en.png new file mode 100644 index 00000000000..0ec3bfe704b Binary files /dev/null and b/public/images/events/valentines2025event-en.png differ diff --git a/public/images/events/valentines2025event-es-ES.png b/public/images/events/valentines2025event-es-ES.png new file mode 100644 index 00000000000..0ec3bfe704b Binary files /dev/null and b/public/images/events/valentines2025event-es-ES.png differ diff --git a/public/images/events/valentines2025event-fr.png b/public/images/events/valentines2025event-fr.png new file mode 100644 index 00000000000..0ec3bfe704b Binary files /dev/null and b/public/images/events/valentines2025event-fr.png differ diff --git a/public/images/events/valentines2025event-it.png b/public/images/events/valentines2025event-it.png new file mode 100644 index 00000000000..0ec3bfe704b Binary files /dev/null and b/public/images/events/valentines2025event-it.png differ diff --git a/public/images/events/valentines2025event-ja.png b/public/images/events/valentines2025event-ja.png new file mode 100644 index 00000000000..0ec3bfe704b Binary files /dev/null and b/public/images/events/valentines2025event-ja.png differ diff --git a/public/images/events/valentines2025event-ko.png b/public/images/events/valentines2025event-ko.png new file mode 100644 index 00000000000..0ec3bfe704b Binary files /dev/null and b/public/images/events/valentines2025event-ko.png differ diff --git a/public/images/events/valentines2025event-pt-BR.png b/public/images/events/valentines2025event-pt-BR.png new file mode 100644 index 00000000000..0ec3bfe704b Binary files /dev/null and b/public/images/events/valentines2025event-pt-BR.png differ diff --git a/public/images/events/valentines2025event-zh-CN.png b/public/images/events/valentines2025event-zh-CN.png new file mode 100644 index 00000000000..0ec3bfe704b Binary files /dev/null and b/public/images/events/valentines2025event-zh-CN.png 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/account.ts b/src/account.ts index 316645b38ff..4c86595a5e6 100644 --- a/src/account.ts +++ b/src/account.ts @@ -11,8 +11,8 @@ export function initLoggedInUser(): void { loggedInUser = { username: "Guest", lastSessionSlot: -1, discordId: "", googleId: "", hasAdminRole: false }; } -export function updateUserInfo(): Promise<[boolean, integer]> { - return new Promise<[boolean, integer]>(resolve => { +export function updateUserInfo(): Promise<[boolean, number]> { + return new Promise<[boolean, number]>(resolve => { if (bypassLogin) { loggedInUser = { username: "Guest", lastSessionSlot: -1, discordId: "", googleId: "", hasAdminRole: false }; let lastSessionSlot = -1; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 2c67c4d2cde..9bfa153ef60 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -118,7 +118,7 @@ export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; const DEBUG_RNG = false; -const OPP_IVS_OVERRIDE_VALIDATED : integer[] = ( +const OPP_IVS_OVERRIDE_VALIDATED : number[] = ( Array.isArray(Overrides.OPP_IVS_OVERRIDE) ? Overrides.OPP_IVS_OVERRIDE : new Array(6).fill(Overrides.OPP_IVS_OVERRIDE) @@ -134,7 +134,7 @@ interface StarterColors { } export interface PokeballCounts { - [pb: string]: integer; + [pb: string]: number; } export type AnySound = Phaser.Sound.WebAudioSound | Phaser.Sound.HTML5AudioSound | Phaser.Sound.NoAudioSound; @@ -149,18 +149,19 @@ export default class BattleScene extends SceneBase { public inputController: InputsController; public uiInputs: UiInputs; - public sessionPlayTime: integer | null = null; - public lastSavePlayTime: integer | null = null; + public sessionPlayTime: number | null = null; + public lastSavePlayTime: number | null = null; public masterVolume: number = 0.5; public bgmVolume: number = 1; public fieldVolume: number = 1; public seVolume: number = 1; public uiVolume: number = 1; - public gameSpeed: integer = 1; - public damageNumbersMode: integer = 0; + public gameSpeed: number = 1; + public damageNumbersMode: number = 0; 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; @@ -176,16 +177,16 @@ export default class BattleScene extends SceneBase { * - 1 = 'Passives Only' * - 2 = 'On' */ - public candyUpgradeNotification: integer = 0; + public candyUpgradeNotification: number = 0; /** * Determines what type of notification is used for Candy Upgrades * - 0 = 'Icon' * - 1 = 'Animation' */ - public candyUpgradeDisplay: integer = 0; + public candyUpgradeDisplay: number = 0; public moneyFormat: MoneyFormat = MoneyFormat.NORMAL; public uiTheme: UiTheme = UiTheme.DEFAULT; - public windowType: integer = 0; + public windowType: number = 0; public experimentalSprites: boolean = false; public musicPreference: number = MusicPreference.ALLGENS; public moveAnimations: boolean = true; @@ -212,7 +213,7 @@ export default class BattleScene extends SceneBase { * @default 0 - Uses the default normal experience gain display. */ public expParty: ExpNotification = 0; - public hpBarSpeed: integer = 0; + public hpBarSpeed: number = 0; public fusionPaletteSwaps: boolean = true; public enableTouchControls: boolean = false; public enableVibration: boolean = false; @@ -223,7 +224,7 @@ export default class BattleScene extends SceneBase { * - 0 = 'Switch' * - 1 = 'Set' - The option to switch the active pokemon at the start of a battle will not display. */ - public battleStyle: integer = BattleStyle.SWITCH; + public battleStyle: number = BattleStyle.SWITCH; /** * Defines whether or not to show type effectiveness hints @@ -235,7 +236,7 @@ export default class BattleScene extends SceneBase { public disableMenu: boolean = false; public gameData: GameData; - public sessionSlotId: integer; + public sessionSlotId: number; /** PhaseQueue: dequeue/remove the first element to get the next phase */ public phaseQueue: Phase[]; @@ -244,7 +245,7 @@ export default class BattleScene extends SceneBase { private phaseQueuePrepend: Phase[]; /** overrides default of inserting phases to end of phaseQueuePrepend array, useful or inserting Phases "out of order" */ - private phaseQueuePrependSpliceIndex: integer; + private phaseQueuePrependSpliceIndex: number; private nextCommandPhaseQueue: Phase[]; private currentPhase: Phase | null; @@ -265,13 +266,13 @@ export default class BattleScene extends SceneBase { public arenaNextEnemy: ArenaBase; public arena: Arena; public gameMode: GameMode; - public score: integer; + public score: number; public lockModifierTiers: boolean; public trainer: Phaser.GameObjects.Sprite; public lastEnemyTrainer: Trainer | null; public currentBattle: Battle; public pokeballCounts: PokeballCounts; - public money: integer; + public money: number; public pokemonInfoContainer: PokemonInfoContainer; private party: PlayerPokemon[]; /** Session save data that pertains to Mystery Encounters */ @@ -300,7 +301,7 @@ export default class BattleScene extends SceneBase { public seed: string; public waveSeed: string; - public waveCycleOffset: integer; + public waveCycleOffset: number; public offsetGym: boolean; public damageNumberHandler: DamageNumberHandler; @@ -314,9 +315,9 @@ export default class BattleScene extends SceneBase { private bgmCache: Set = new Set(); private playTimeTimer: Phaser.Time.TimerEvent; - public rngCounter: integer = 0; + public rngCounter: number = 0; public rngSeedOverride: string = ""; - public rngOffset: integer = 0; + public rngOffset: number = 0; public inputMethod: string; private infoToggles: InfoToggle[] = []; @@ -717,7 +718,7 @@ export default class BattleScene extends SceneBase { const traverseVariantData = (keys: string[]) => { let variantTree = variantData; let expTree = expVariantData; - keys.map((k: string, i: integer) => { + keys.map((k: string, i: number) => { if (i < keys.length - 1) { variantTree = variantTree[k]; expTree = expTree[k]; @@ -926,12 +927,12 @@ export default class BattleScene extends SceneBase { return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles; } - getPokemonById(pokemonId: integer): Pokemon | null { + getPokemonById(pokemonId: number): Pokemon | null { const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId); return (findInParty(this.getPlayerParty()) || findInParty(this.getEnemyParty())) ?? null; } - addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon { + addPlayerPokemon(species: PokemonSpecies, level: number, abilityIndex?: number, formIndex?: number, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: number[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon { const pokemon = new PlayerPokemon(species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); if (postProcess) { postProcess(pokemon); @@ -940,7 +941,7 @@ export default class BattleScene extends SceneBase { return pokemon; } - addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, shinyLock: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon { + addEnemyPokemon(species: PokemonSpecies, level: number, trainerSlot: TrainerSlot, boss: boolean = false, shinyLock: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon { if (Overrides.OPP_LEVEL_OVERRIDE > 0) { level = Overrides.OPP_LEVEL_OVERRIDE; } @@ -1092,7 +1093,7 @@ export default class BattleScene extends SceneBase { * @param min The minimum integer to pick, default `0` * @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) */ - randBattleSeedInt(range: integer, min: integer = 0): integer { + randBattleSeedInt(range: number, min: number = 0): number { return this.currentBattle?.randSeedInt(range, min); } @@ -1217,7 +1218,7 @@ export default class BattleScene extends SceneBase { return Math.max(doubleChance.value, 1); } - newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounterType?: MysteryEncounterType): Battle | null { + newBattle(waveIndex?: number, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounterType?: MysteryEncounterType): Battle | null { const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave; const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1); let newDouble: boolean | undefined; @@ -1443,7 +1444,7 @@ export default class BattleScene extends SceneBase { }); } - getSpeciesFormIndex(species: PokemonSpecies, gender?: Gender, nature?: Nature, ignoreArena?: boolean): integer { + getSpeciesFormIndex(species: PokemonSpecies, gender?: Gender, nature?: Nature, ignoreArena?: boolean): number { if (!species.forms?.length) { return 0; } @@ -1540,7 +1541,7 @@ export default class BattleScene extends SceneBase { return ret; } - private getGeneratedWaveCycleOffset(): integer { + private getGeneratedWaveCycleOffset(): number { let ret = 0; this.executeWithSeedOffset(() => { ret = Utils.randSeedInt(8) * 5; @@ -1548,7 +1549,7 @@ export default class BattleScene extends SceneBase { return ret; } - getEncounterBossSegments(waveIndex: integer, level: integer, species?: PokemonSpecies, forceBoss: boolean = false): integer { + getEncounterBossSegments(waveIndex: number, level: number, species?: PokemonSpecies, forceBoss: boolean = false): number { if (Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE > 1) { return Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE; } else if (Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE === 1) { @@ -1572,7 +1573,7 @@ export default class BattleScene extends SceneBase { return 0; } - let ret: integer = 2; + let ret: number = 2; if (level >= 100) { ret++; @@ -1589,7 +1590,7 @@ export default class BattleScene extends SceneBase { trySpreadPokerus(): void { const party = this.getPlayerParty(); - const infectedIndexes: integer[] = []; + const infectedIndexes: number[] = []; const spread = (index: number, spreadTo: number) => { const partyMember = party[index + spreadTo]; if (!partyMember.pokerus && !Utils.randSeedInt(10)) { @@ -1613,7 +1614,7 @@ export default class BattleScene extends SceneBase { }); } - resetSeed(waveIndex?: integer): void { + resetSeed(waveIndex?: number): void { const wave = waveIndex || this.currentBattle?.waveIndex || 0; this.waveSeed = Utils.shiftCharCodes(this.seed, wave); Phaser.Math.RND.sow([ this.waveSeed ]); @@ -1621,7 +1622,7 @@ export default class BattleScene extends SceneBase { this.rngCounter = 0; } - executeWithSeedOffset(func: Function, offset: integer, seedOverride?: string): void { + executeWithSeedOffset(func: Function, offset: number, seedOverride?: string): void { if (!func) { return; } @@ -1669,7 +1670,7 @@ export default class BattleScene extends SceneBase { this.arenaFlyout.toggleFlyout(pressed); } - showFieldOverlay(duration: integer): Promise { + showFieldOverlay(duration: number): Promise { return new Promise(resolve => { this.tweens.add({ targets: this.fieldOverlay, @@ -1681,7 +1682,7 @@ export default class BattleScene extends SceneBase { }); } - hideFieldOverlay(duration: integer): Promise { + hideFieldOverlay(duration: number): Promise { return new Promise(resolve => { this.tweens.add({ targets: this.fieldOverlay, @@ -1701,7 +1702,7 @@ export default class BattleScene extends SceneBase { } } - showShopOverlay(duration: integer): Promise { + showShopOverlay(duration: number): Promise { this.shopOverlayShown = true; return new Promise(resolve => { this.tweens.add({ @@ -1714,7 +1715,7 @@ export default class BattleScene extends SceneBase { }); } - hideShopOverlay(duration: integer): Promise { + hideShopOverlay(duration: number): Promise { this.shopOverlayShown = false; return new Promise(resolve => { this.tweens.add({ @@ -1803,7 +1804,7 @@ export default class BattleScene extends SceneBase { }); } - hideLuckText(duration: integer): void { + hideLuckText(duration: number): void { if (this.reroll) { return; } @@ -1843,7 +1844,7 @@ export default class BattleScene extends SceneBase { } addFaintedEnemyScore(enemy: EnemyPokemon): void { - let scoreIncrease = enemy.getSpeciesForm().getBaseExp() * (enemy.level / this.getMaxExpLevel()) * ((enemy.ivs.reduce((iv: integer, total: integer) => total += iv, 0) / 93) * 0.2 + 0.8); + let scoreIncrease = enemy.getSpeciesForm().getBaseExp() * (enemy.level / this.getMaxExpLevel()) * ((enemy.ivs.reduce((iv: number, total: number) => total += iv, 0) / 93) * 0.2 + 0.8); this.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemy.id, false).map(m => scoreIncrease *= (m as PokemonHeldItemModifier).getScoreMultiplier()); if (enemy.isBoss()) { scoreIncrease *= Math.sqrt(enemy.bossSegments); @@ -1851,7 +1852,7 @@ export default class BattleScene extends SceneBase { this.currentBattle.battleScore += Math.ceil(scoreIncrease); } - getMaxExpLevel(ignoreLevelCap: boolean = false): integer { + getMaxExpLevel(ignoreLevelCap: boolean = false): number { if (Overrides.LEVEL_CAP_OVERRIDE > 0) { return Overrides.LEVEL_CAP_OVERRIDE; } else if (ignoreLevelCap || Overrides.LEVEL_CAP_OVERRIDE < 0) { @@ -1863,7 +1864,7 @@ export default class BattleScene extends SceneBase { return Math.ceil(baseLevel / 2) * 2 + 2; } - randomSpecies(waveIndex: integer, level: integer, fromArenaPool?: boolean, speciesFilter?: PokemonSpeciesFilter, filterAllEvolutions?: boolean): PokemonSpecies { + randomSpecies(waveIndex: number, level: number, fromArenaPool?: boolean, speciesFilter?: PokemonSpeciesFilter, filterAllEvolutions?: boolean): PokemonSpecies { if (fromArenaPool) { return this.arena.randomSpecies(waveIndex, level, undefined, getPartyLuckValue(this.party)); } @@ -1878,13 +1879,13 @@ export default class BattleScene extends SceneBase { return filteredSpecies[Utils.randSeedInt(filteredSpecies.length)]; } - generateRandomBiome(waveIndex: integer): Biome { + generateRandomBiome(waveIndex: number): Biome { const relWave = waveIndex % 250; const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END); const maxDepth = biomeDepths[Biome.END][0] - 2; const depthWeights = new Array(maxDepth + 1).fill(null) - .map((_, i: integer) => ((1 - Math.min(Math.abs((i / (maxDepth - 1)) - (relWave / 250)) + 0.25, 1)) / 0.75) * 250); - const biomeThresholds: integer[] = []; + .map((_, i: number) => ((1 - Math.min(Math.abs((i / (maxDepth - 1)) - (relWave / 250)) + 0.25, 1)) / 0.75) * 250); + const biomeThresholds: number[] = []; let totalWeight = 0; for (const biome of biomes) { totalWeight += Math.ceil(depthWeights[biomeDepths[biome][0] - 1] / biomeDepths[biome][1]); @@ -2008,7 +2009,7 @@ export default class BattleScene extends SceneBase { } } - fadeOutBgm(duration: integer = 500, destroy: boolean = true): boolean { + fadeOutBgm(duration: number = 500, destroy: boolean = true): boolean { if (!this.bgm) { return false; } @@ -2075,7 +2076,7 @@ export default class BattleScene extends SceneBase { } } - playSoundWithoutBgm(soundName: string, pauseDuration?: integer): AnySound { + playSoundWithoutBgm(soundName: string, pauseDuration?: number): AnySound { this.bgmCache.add(soundName); const resumeBgm = this.pauseBgm(); this.playSound(soundName); @@ -2535,7 +2536,7 @@ export default class BattleScene extends SceneBase { * @param promptDelay optional param for MessagePhase constructor * @param defer boolean for which queue to add it to, false -> add to PhaseQueuePrepend, true -> nextCommandPhaseQueue */ - queueMessage(message: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null, defer?: boolean | null) { + queueMessage(message: string, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null, defer?: boolean | null) { const phase = new MessagePhase(message, callbackDelay, prompt, promptDelay); if (!defer) { // adds to the end of PhaseQueuePrepend @@ -2557,14 +2558,14 @@ export default class BattleScene extends SceneBase { this.phaseQueue.push(new TurnInitPhase()); } - addMoney(amount: integer): void { + addMoney(amount: number): void { this.money = Math.min(this.money + amount, Number.MAX_SAFE_INTEGER); this.updateMoneyText(); this.animateMoneyChanged(true); this.validateAchvs(MoneyAchv); } - getWaveMoneyAmount(moneyMultiplier: number): integer { + getWaveMoneyAmount(moneyMultiplier: number): number { const waveIndex = this.currentBattle.waveIndex; const waveSetIndex = Math.ceil(waveIndex / 10) - 1; const moneyValue = Math.pow((waveSetIndex + 1 + (0.75 + (((waveIndex - 1) % 10) + 1) / 10)) * 100, 1 + 0.005 * waveSetIndex) * moneyMultiplier; @@ -2761,7 +2762,7 @@ export default class BattleScene extends SceneBase { }); } - removePartyMemberModifiers(partyMemberIndex: integer): Promise { + removePartyMemberModifiers(partyMemberIndex: number): Promise { return new Promise(resolve => { const pokemonId = this.getPlayerParty()[partyMemberIndex].id; const modifiersToRemove = this.modifiers.filter(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemonId); @@ -2793,7 +2794,7 @@ export default class BattleScene extends SceneBase { } } - party.forEach((enemyPokemon: EnemyPokemon, i: integer) => { + party.forEach((enemyPokemon: EnemyPokemon, i: number) => { if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i]) { heldModifiersConfigs[i].forEach(mt => { let modifier: PokemonHeldItemModifier; diff --git a/src/battle.ts b/src/battle.ts index fa333040c22..3f36865c74b 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -101,6 +101,9 @@ export default class Battle { public battleSeed: string = Utils.randomString(16, true); private battleSeedState: string | null = null; public moneyScattered: number = 0; + /** Primarily for double battles, keeps track of last enemy and player pokemon that triggered its ability or used a move */ + public lastEnemyInvolved: number; + public lastPlayerInvolved: number; public lastUsedPokeball: PokeballType | null = null; /** The number of times a Pokemon on the player's side has fainted this battle */ public playerFaints: number = 0; diff --git a/src/data/ability.ts b/src/data/ability.ts index 7cdf1924103..a9d3939d0e5 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -51,13 +51,13 @@ export class Ability implements Localizable { private nameAppend: string; public name: string; public description: string; - public generation: integer; + public generation: number; public isBypassFaint: boolean; public isIgnorable: boolean; public attrs: AbAttr[]; public conditions: AbAttrCondition[]; - constructor(id: Abilities, generation: integer) { + constructor(id: Abilities, generation: number) { this.id = id; this.nameAppend = ""; @@ -221,9 +221,9 @@ export class PostBattleInitAbAttr extends AbAttr { } export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr { - private formFunc: (p: Pokemon) => integer; + private formFunc: (p: Pokemon) => number; - constructor(formFunc: ((p: Pokemon) => integer)) { + constructor(formFunc: ((p: Pokemon) => number)) { super(true); this.formFunc = formFunc; @@ -491,9 +491,9 @@ class TypeImmunityStatStageChangeAbAttr extends TypeImmunityAbAttr { class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr { private tagType: BattlerTagType; - private turnCount: integer; + private turnCount: number; - constructor(immuneType: Type, tagType: BattlerTagType, turnCount: integer, condition?: AbAttrCondition) { + constructor(immuneType: Type, tagType: BattlerTagType, turnCount: number, condition?: AbAttrCondition) { super(immuneType, condition); this.tagType = tagType; @@ -605,7 +605,7 @@ export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { } export class PostStatStageChangeAbAttr extends AbAttr { - applyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statsChanged: BattleStat[], stagesChanged: integer, selfTarget: boolean, args: any[]): boolean | Promise { + applyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statsChanged: BattleStat[], stagesChanged: number, selfTarget: boolean, args: any[]): boolean | Promise { return false; } } @@ -866,10 +866,10 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { } export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { - public chance: integer; + public chance: number; private effects: StatusEffect[]; - constructor(chance: integer, ...effects: StatusEffect[]) { + constructor(chance: number, ...effects: StatusEffect[]) { super(); this.chance = chance; @@ -905,11 +905,11 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr } export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { - private chance: integer; + private chance: number; private tagType: BattlerTagType; - private turnCount: integer | undefined; + private turnCount: number | undefined; - constructor(chance: integer, tagType: BattlerTagType, turnCount?: integer) { + constructor(chance: number, tagType: BattlerTagType, turnCount?: number) { super(); this.tagType = tagType; @@ -959,9 +959,9 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { } export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { - private damageRatio: integer; + private damageRatio: number; - constructor(damageRatio: integer) { + constructor(damageRatio: number) { super(); this.damageRatio = damageRatio; @@ -993,9 +993,9 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { * @extends {PostDefendAbAttr} */ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { - private turns: integer; + private turns: number; - constructor(turns: integer) { + constructor(turns: number) { super(); this.turns = turns; @@ -1101,11 +1101,11 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { } export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { - private chance: integer; + private chance: number; private attacker: Pokemon; private move: Move; - constructor(chance: integer) { + constructor(chance: number) { super(); this.chance = chance; @@ -1688,10 +1688,10 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { private contactRequired: boolean; - private chance: integer; + private chance: number; private effects: StatusEffect[]; - constructor(contactRequired: boolean, chance: integer, ...effects: StatusEffect[]) { + constructor(contactRequired: boolean, chance: number, ...effects: StatusEffect[]) { super(); this.contactRequired = contactRequired; @@ -1715,18 +1715,18 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { } export class PostAttackContactApplyStatusEffectAbAttr extends PostAttackApplyStatusEffectAbAttr { - constructor(chance: integer, ...effects: StatusEffect[]) { + constructor(chance: number, ...effects: StatusEffect[]) { super(true, chance, ...effects); } } export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { private contactRequired: boolean; - private chance: (user: Pokemon, target: Pokemon, move: Move) => integer; + private chance: (user: Pokemon, target: Pokemon, move: Move) => number; private effects: BattlerTagType[]; - constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: Move) => integer, ...effects: BattlerTagType[]) { + constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: Move) => number, ...effects: BattlerTagType[]) { super(); this.contactRequired = contactRequired; @@ -1863,9 +1863,9 @@ class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr { } export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr { - private formFunc: (p: Pokemon) => integer; + private formFunc: (p: Pokemon) => number; - constructor(formFunc: ((p: Pokemon) => integer)) { + constructor(formFunc: ((p: Pokemon) => number)) { super(true); this.formFunc = formFunc; @@ -2081,9 +2081,9 @@ export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr { export class PostSummonAddBattlerTagAbAttr extends PostSummonAbAttr { private tagType: BattlerTagType; - private turnCount: integer; + private turnCount: number; - constructor(tagType: BattlerTagType, turnCount: integer, showAbility?: boolean) { + constructor(tagType: BattlerTagType, turnCount: number, showAbility?: boolean) { super(showAbility); this.tagType = tagType; @@ -2209,9 +2209,9 @@ export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr { * @see {applyPostSummon} */ export class DownloadAbAttr extends PostSummonAbAttr { - private enemyDef: integer; - private enemySpDef: integer; - private enemyCountTally: integer; + private enemyDef: number; + private enemySpDef: number; + private enemyCountTally: number; private stats: BattleStat[]; /** @@ -2295,9 +2295,9 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr { } export class PostSummonFormChangeAbAttr extends PostSummonAbAttr { - private formFunc: (p: Pokemon) => integer; + private formFunc: (p: Pokemon) => number; - constructor(formFunc: ((p: Pokemon) => integer)) { + constructor(formFunc: ((p: Pokemon) => number)) { super(true); this.formFunc = formFunc; @@ -2644,18 +2644,75 @@ export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { } } + +export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { + applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + if (!pokemon.isFullHp()) { + if (!simulated) { + const healAmount = Utils.toDmgValue(pokemon.getMaxHp() * 0.33); + pokemon.heal(healAmount); + pokemon.updateInfo(); + } + + return true; + } + + return false; + } +} + +/** + * Attribute for form changes that occur on switching out + * @extends PreSwitchOutAbAttr + * @see {@linkcode applyPreSwitchOut} + */ +export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr { + private formFunc: (p: Pokemon) => number; + + constructor(formFunc: ((p: Pokemon) => number)) { + super(); + + this.formFunc = formFunc; + } + + /** + * On switch out, trigger the form change to the one defined in the ability + * @param pokemon The pokemon switching out and changing form {@linkcode Pokemon} + * @param passive N/A + * @param args N/A + * @returns true if the form change was successful + */ + applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + const formIndex = this.formFunc(pokemon); + if (formIndex !== pokemon.formIndex) { + if (!simulated) { + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); + } + return true; + } + + return false; + } + +} + +export class PreLeaveFieldAbAttr extends AbAttr { + applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + return false; + } +} + /** * Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon switching out. */ -export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { - +export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr { /** * @param pokemon The {@linkcode Pokemon} with the ability * @param passive N/A * @param args N/A - * @returns {boolean} Returns true if the weather clears, otherwise false. + * @returns Returns `true` if the weather clears, otherwise `false`. */ - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { const weatherType = globalScene.arena.weather?.weatherType; let turnOffWeather = false; @@ -2694,60 +2751,47 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { } } -export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { - if (!pokemon.isFullHp()) { - if (!simulated) { - const healAmount = Utils.toDmgValue(pokemon.getMaxHp() * 0.33); - pokemon.heal(healAmount); - pokemon.updateInfo(); - } - - return true; - } - +export class PreStatStageChangeAbAttr extends AbAttr { + applyPreStatStageChange(pokemon: Pokemon | null, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { return false; } } /** - * Attribute for form changes that occur on switching out - * @extends PreSwitchOutAbAttr - * @see {@linkcode applyPreSwitchOut} + * Reflect all {@linkcode BattleStat} reductions caused by other Pokémon's moves and Abilities. + * Currently only applies to Mirror Armor. */ -export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr { - private formFunc: (p: Pokemon) => integer; - - constructor(formFunc: ((p: Pokemon) => integer)) { - super(); - - this.formFunc = formFunc; - } +export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr { + /** {@linkcode BattleStat} to reflect */ + private reflectedStat? : BattleStat; /** - * On switch out, trigger the form change to the one defined in the ability - * @param pokemon The pokemon switching out and changing form {@linkcode Pokemon} - * @param passive N/A - * @param args N/A - * @returns true if the form change was successful + * Apply the {@linkcode ReflectStatStageChangeAbAttr} to an interaction + * @param _pokemon The user pokemon + * @param _passive N/A + * @param simulated `true` if the ability is being simulated by the AI + * @param stat the {@linkcode BattleStat} being affected + * @param cancelled The {@linkcode Utils.BooleanHolder} that will be set to true due to reflection + * @param args + * @returns true because it reflects any stat being lowered */ - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { - const formIndex = this.formFunc(pokemon); - if (formIndex !== pokemon.formIndex) { - if (!simulated) { - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); - } - return true; + applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { + const attacker: Pokemon = args[0]; + const stages = args[1]; + this.reflectedStat = stat; + if (!simulated) { + globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [ stat ], stages, true, false, true, null, true)); } - - return false; + cancelled.value = true; + return true; } -} - -export class PreStatStageChangeAbAttr extends AbAttr { - applyPreStatStageChange(pokemon: Pokemon | null, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { - return false; + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + return i18next.t("abilityTriggers:protectStat", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + statName: this.reflectedStat ? i18next.t(getStatKey(this.reflectedStat)) : i18next.t("battle:stats") + }); } } @@ -3339,10 +3383,10 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { export class PostWeatherChangeAddBattlerTagAttr extends PostWeatherChangeAbAttr { private tagType: BattlerTagType; - private turnCount: integer; + private turnCount: number; private weatherTypes: WeatherType[]; - constructor(tagType: BattlerTagType, turnCount: integer, ...weatherTypes: WeatherType[]) { + constructor(tagType: BattlerTagType, turnCount: number, ...weatherTypes: WeatherType[]) { super(); this.tagType = tagType; @@ -3383,9 +3427,9 @@ export class PostWeatherLapseAbAttr extends AbAttr { } export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr { - private healFactor: integer; + private healFactor: number; - constructor(healFactor: integer, ...weatherTypes: WeatherType[]) { + constructor(healFactor: number, ...weatherTypes: WeatherType[]) { super(...weatherTypes); this.healFactor = healFactor; @@ -3406,9 +3450,9 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr { } export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { - private damageFactor: integer; + private damageFactor: number; - constructor(damageFactor: integer, ...weatherTypes: WeatherType[]) { + constructor(damageFactor: number, ...weatherTypes: WeatherType[]) { super(...weatherTypes); this.damageFactor = damageFactor; @@ -3437,10 +3481,10 @@ export class PostTerrainChangeAbAttr extends AbAttr { export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr { private tagType: BattlerTagType; - private turnCount: integer; + private turnCount: number; private terrainTypes: TerrainType[]; - constructor(tagType: BattlerTagType, turnCount: integer, ...terrainTypes: TerrainType[]) { + constructor(tagType: BattlerTagType, turnCount: number, ...terrainTypes: TerrainType[]) { super(); this.tagType = tagType; @@ -3693,9 +3737,9 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr { } export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { - private formFunc: (p: Pokemon) => integer; + private formFunc: (p: Pokemon) => number; - constructor(formFunc: ((p: Pokemon) => integer)) { + constructor(formFunc: ((p: Pokemon) => number)) { super(true); this.formFunc = formFunc; @@ -3917,9 +3961,9 @@ export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr { } export class StatStageChangeMultiplierAbAttr extends AbAttr { - private multiplier: integer; + private multiplier: number; - constructor(multiplier: integer) { + constructor(multiplier: number) { super(true); this.multiplier = multiplier; @@ -4172,63 +4216,10 @@ export class PostFaintUnsuppressedWeatherFormChangeAbAttr extends PostFaintAbAtt } } -/** - * Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon fainting - */ -export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr { - - /** - * @param pokemon The {@linkcode Pokemon} with the ability - * @param passive N/A - * @param attacker N/A - * @param move N/A - * @param hitResult N/A - * @param args N/A - * @returns {boolean} Returns true if the weather clears, otherwise false. - */ - applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { - const weatherType = globalScene.arena.weather?.weatherType; - let turnOffWeather = false; - - // Clear weather only if user's ability matches the weather and no other pokemon has the ability. - switch (weatherType) { - case (WeatherType.HARSH_SUN): - if (pokemon.hasAbility(Abilities.DESOLATE_LAND) - && globalScene.getField(true).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.HEAVY_RAIN): - if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) - && globalScene.getField(true).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.STRONG_WINDS): - if (pokemon.hasAbility(Abilities.DELTA_STREAM) - && globalScene.getField(true).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { - turnOffWeather = true; - } - break; - } - - if (simulated) { - return turnOffWeather; - } - - if (turnOffWeather) { - globalScene.arena.trySetWeather(WeatherType.NONE, false); - return true; - } - - return false; - } -} - export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { - private damageRatio: integer; + private damageRatio: number; - constructor(damageRatio: integer) { + constructor(damageRatio: number) { super(); this.damageRatio = damageRatio; @@ -4405,9 +4396,9 @@ export class ReduceBerryUseThresholdAbAttr extends AbAttr { * Used for Heavy Metal (doubling weight) and Light Metal (halving weight) */ export class WeightMultiplierAbAttr extends AbAttr { - private multiplier: integer; + private multiplier: number; - constructor(multiplier: integer) { + constructor(multiplier: number) { super(); this.multiplier = multiplier; @@ -4710,12 +4701,12 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { * @extends AbAttr */ export class BypassSpeedChanceAbAttr extends AbAttr { - public chance: integer; + public chance: number; /** - * @param {integer} chance probability of ability being active. + * @param {number} chance probability of ability being active. */ - constructor(chance: integer) { + constructor(chance: number) { super(true); this.chance = chance; } @@ -5241,13 +5232,18 @@ export function applyPreSwitchOutAbAttrs(attrType: Constructor(attrType, pokemon, (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, simulated, args), args, true, simulated); } +export function applyPreLeaveFieldAbAttrs(attrType: Constructor, + pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, args), args, true, simulated); +} + export function applyPreStatStageChangeAbAttrs(attrType: Constructor, pokemon: Pokemon | null, stat: BattleStat, cancelled: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), args, false, simulated); } export function applyPostStatStageChangeAbAttrs(attrType: Constructor, - pokemon: Pokemon, stats: BattleStat[], stages: integer, selfTarget: boolean, simulated: boolean = false, ...args: any[]): Promise { + pokemon: Pokemon, stats: BattleStat[], stages: number, selfTarget: boolean, simulated: boolean = false, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, (attr, _passive) => attr.applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), args, false, simulated); } @@ -5939,20 +5935,17 @@ export function initAbilities() { new Ability(Abilities.PRIMORDIAL_SEA, 6) .attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HEAVY_RAIN) - .attr(PreSwitchOutClearWeatherAbAttr) - .attr(PostFaintClearWeatherAbAttr) + .attr(PreLeaveFieldClearWeatherAbAttr) .bypassFaint(), new Ability(Abilities.DESOLATE_LAND, 6) .attr(PostSummonWeatherChangeAbAttr, WeatherType.HARSH_SUN) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HARSH_SUN) - .attr(PreSwitchOutClearWeatherAbAttr) - .attr(PostFaintClearWeatherAbAttr) + .attr(PreLeaveFieldClearWeatherAbAttr) .bypassFaint(), new Ability(Abilities.DELTA_STREAM, 6) .attr(PostSummonWeatherChangeAbAttr, WeatherType.STRONG_WINDS) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.STRONG_WINDS) - .attr(PreSwitchOutClearWeatherAbAttr) - .attr(PostFaintClearWeatherAbAttr) + .attr(PreLeaveFieldClearWeatherAbAttr) .bypassFaint(), new Ability(Abilities.STAMINA, 7) .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1), @@ -6137,8 +6130,8 @@ export function initAbilities() { new Ability(Abilities.PROPELLER_TAIL, 8) .attr(BlockRedirectAbAttr), new Ability(Abilities.MIRROR_ARMOR, 8) - .ignorable() - .unimplemented(), + .attr(ReflectStatStageChangeAbAttr) + .ignorable(), /** * Right now, the logic is attached to Surf and Dive moves. Ideally, the post-defend/hit should be an * ability attribute but the current implementation of move effects for BattlerTag does not support this- in the case @@ -6316,9 +6309,8 @@ export function initAbilities() { .attr(NoTransformAbilityAbAttr) .partial(), // While setting the tag, the getbattlestat should ignore all modifiers to stats except stat stages new Ability(Abilities.GOOD_AS_GOLD, 9) - .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.category === MoveCategory.STATUS) - .ignorable() - .partial(), // Lots of weird interactions with moves and abilities such as negating status moves that target the field + .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.category === MoveCategory.STATUS && ![ MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES, MoveTarget.USER_SIDE ].includes(move.moveTarget)) + .ignorable(), new Ability(Abilities.VESSEL_OF_RUIN, 9) .attr(FieldMultiplyStatAbAttr, Stat.SPATK, 0.75) .attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonVesselOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.SPATK)) })) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 816de3e824c..2fa4593fd6c 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -910,7 +910,7 @@ class StickyWebTag extends ArenaTrapTag { if (!cancelled.value) { globalScene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() })); const stages = new NumberHolder(-1); - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value, true, false, true, null, false, true)); return true; } } diff --git a/src/data/balance/biomes.ts b/src/data/balance/biomes.ts index 8913fd47e93..5b5e69b4042 100644 --- a/src/data/balance/biomes.ts +++ b/src/data/balance/biomes.ts @@ -26,11 +26,11 @@ export function getBiomeName(biome: Biome | -1) { } interface BiomeLinks { - [key: integer]: Biome | (Biome | [Biome, integer])[] + [key: number]: Biome | (Biome | [Biome, number])[] } interface BiomeDepths { - [key: integer]: [integer, integer] + [key: number]: [number, number] } export const biomeLinks: BiomeLinks = { @@ -87,19 +87,19 @@ export enum BiomePoolTier { export const uncatchableSpecies: Species[] = []; export interface SpeciesTree { - [key: integer]: Species[] + [key: number]: Species[] } export interface PokemonPools { - [key: integer]: (Species | SpeciesTree)[] + [key: number]: (Species | SpeciesTree)[] } export interface BiomeTierPokemonPools { - [key: integer]: PokemonPools + [key: number]: PokemonPools } export interface BiomePokemonPools { - [key: integer]: BiomeTierPokemonPools + [key: number]: BiomeTierPokemonPools } export interface BiomeTierTod { @@ -109,17 +109,17 @@ export interface BiomeTierTod { } export interface CatchableSpecies{ - [key: integer]: BiomeTierTod[] + [key: number]: BiomeTierTod[] } export const catchableSpecies: CatchableSpecies = {}; export interface BiomeTierTrainerPools { - [key: integer]: TrainerType[] + [key: number]: TrainerType[] } export interface BiomeTrainerPools { - [key: integer]: BiomeTierTrainerPools + [key: number]: BiomeTierTrainerPools } export const biomePokemonPools: BiomePokemonPools = { @@ -7675,15 +7675,15 @@ export function initBiomes() { biomeDepths[Biome.TOWN] = [ 0, 1 ]; - const traverseBiome = (biome: Biome, depth: integer) => { + const traverseBiome = (biome: Biome, depth: number) => { if (biome === Biome.END) { const biomeList = Object.keys(Biome).filter(key => !isNaN(Number(key))); biomeList.pop(); // Removes Biome.END from the list const randIndex = Utils.randSeedInt(biomeList.length, 1); // Will never be Biome.TOWN biome = Biome[biomeList[randIndex]]; } - const linkedBiomes: (Biome | [ Biome, integer ])[] = Array.isArray(biomeLinks[biome]) - ? biomeLinks[biome] as (Biome | [ Biome, integer ])[] + const linkedBiomes: (Biome | [ Biome, number ])[] = Array.isArray(biomeLinks[biome]) + ? biomeLinks[biome] as (Biome | [ Biome, number ])[] : [ biomeLinks[biome] as Biome ]; for (const linkedBiomeEntry of linkedBiomes) { const linkedBiome = !Array.isArray(linkedBiomeEntry) @@ -7700,7 +7700,7 @@ export function initBiomes() { }; traverseBiome(Biome.TOWN, 0); - biomeDepths[Biome.END] = [ Object.values(biomeDepths).map(d => d[0]).reduce((max: integer, value: integer) => Math.max(max, value), 0) + 1, 1 ]; + biomeDepths[Biome.END] = [ Object.values(biomeDepths).map(d => d[0]).reduce((max: number, value: number) => Math.max(max, value), 0) + 1, 1 ]; for (const biome of Utils.getEnumValues(Biome)) { biomePokemonPools[biome] = {}; 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 f28b8bbdeea..df347909d49 100644 --- a/src/data/balance/passives.ts +++ b/src/data/balance/passives.ts @@ -6,13 +6,13 @@ export interface PassiveAbilities { } interface StarterPassiveAbilities { - [key: integer]: PassiveAbilities + [key: number]: PassiveAbilities } 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 a8fe3b5f4ab..0e101c7155b 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -77,9 +77,9 @@ export enum EvolutionItem { /** * Pokemon Evolution tuple type consisting of: * @property 0 {@linkcode Species} The species of the Pokemon. - * @property 1 {@linkcode integer} The level at which the Pokemon evolves. + * @property 1 {@linkcode number} The level at which the Pokemon evolves. */ -export type EvolutionLevel = [species: Species, level: integer]; +export type EvolutionLevel = [species: Species, level: number]; export type EvolutionConditionPredicate = (p: Pokemon) => boolean; export type EvolutionConditionEnforceFunc = (p: Pokemon) => void; @@ -88,12 +88,13 @@ export class SpeciesFormEvolution { public speciesId: Species; public preFormKey: string | null; public evoFormKey: string | null; - public level: integer; + public level: number; public item: EvolutionItem | null; public condition: SpeciesEvolutionCondition | null; public wildDelay: SpeciesWildEvolutionDelay; + public description: string = ""; - constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: integer, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) { + constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) { this.speciesId = speciesId; this.preFormKey = preFormKey; this.evoFormKey = evoFormKey; @@ -101,11 +102,28 @@ 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")); } } export class SpeciesEvolution extends SpeciesFormEvolution { - constructor(speciesId: Species, level: integer, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) { + constructor(speciesId: Species, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) { super(speciesId, null, null, level, item, condition, wildDelay); } } @@ -169,7 +187,7 @@ class MoveEvolutionCondition extends SpeciesEvolutionCondition { } class FriendshipEvolutionCondition extends SpeciesEvolutionCondition { - public amount: integer; + public amount: number; constructor(amount: number) { super(p => p.friendship >= amount); this.amount = amount; @@ -178,7 +196,7 @@ class FriendshipEvolutionCondition extends SpeciesEvolutionCondition { } class FriendshipTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { - public amount: integer; + public amount: number; public timesOfDay: TimeOfDay[]; constructor(amount: number, tod: "day" | "night") { if (tod === "day") { @@ -197,7 +215,7 @@ class FriendshipTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { } class FriendshipMoveTypeEvolutionCondition extends SpeciesEvolutionCondition { - public amount: integer; + public amount: number; public type: Type; constructor(amount: number, type: Type) { super(p => p.friendship >= amount && !!p.getMoveset().find(m => m?.getMove().type === type)); @@ -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/pokemon-level-moves.ts b/src/data/balance/pokemon-level-moves.ts index 8fe61da35c0..dcbc2fb0c0d 100644 --- a/src/data/balance/pokemon-level-moves.ts +++ b/src/data/balance/pokemon-level-moves.ts @@ -1,18 +1,18 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -export type LevelMoves = ([integer, Moves])[]; +export type LevelMoves = ([number, Moves])[]; interface PokemonSpeciesLevelMoves { - [key: integer]: LevelMoves + [key: number]: LevelMoves } interface PokemonFormLevelMoves { - [key: integer]: LevelMoves + [key: number]: LevelMoves } interface PokemonSpeciesFormLevelMoves { - [key: integer]: PokemonFormLevelMoves + [key: number]: PokemonFormLevelMoves } /** Moves that can only be learned with a memory-mushroom */ 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/balance/tms.ts b/src/data/balance/tms.ts index e44f7bf44bf..788ffd4f273 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -3,7 +3,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; interface TmSpecies { - [key: integer]: Array> + [key: number]: Array> } export const reverseCompatibleTms: Moves[] = [];/*[ @@ -68434,7 +68434,7 @@ export const tmSpecies: TmSpecies = { }; interface SpeciesTmMoves { - [key: integer]: (Moves | [string | Species, Moves])[]; + [key: number]: (Moves | [string | Species, Moves])[]; } function transposeTmSpecies(): SpeciesTmMoves { @@ -68474,7 +68474,7 @@ function transposeTmSpecies(): SpeciesTmMoves { export const speciesTmMoves: SpeciesTmMoves = transposeTmSpecies(); interface TmPoolTiers { - [key: integer]: ModifierTier + [key: number]: ModifierTier } export const tmPoolTiers: TmPoolTiers = { diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index c3da8db57c4..99e9e82d4a6 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -106,15 +106,15 @@ export enum CommonAnim { } export class AnimConfig { - public id: integer; + public id: number; public graphic: string; public frames: AnimFrame[][]; - public frameTimedEvents: Map; - public position: integer; - public hue: integer; + public frameTimedEvents: Map; + public position: number; + public hue: number; constructor(source?: any) { - this.frameTimedEvents = new Map; + this.frameTimedEvents = new Map; if (source) { this.id = source.id; @@ -195,18 +195,18 @@ class AnimFrame { public visible: boolean; public blendType: AnimBlendType; public target: AnimFrameTarget; - public graphicFrame: integer; - public opacity: integer; - public color: integer[]; - public tone: integer[]; - public flash: integer[]; + public graphicFrame: number; + public opacity: number; + public color: number[]; + public tone: number[]; + public flash: number[]; public locked: boolean; - public priority: integer; + public priority: number; public focus: AnimFocus; - constructor(x: number, y: number, zoomX: number, zoomY: number, angle: number, mirror: boolean, visible: boolean, blendType: AnimBlendType, pattern: integer, - opacity: integer, colorR: integer, colorG: integer, colorB: integer, colorA: integer, toneR: integer, toneG: integer, toneB: integer, toneA: integer, - flashR: integer, flashG: integer, flashB: integer, flashA: integer, locked: boolean, priority: integer, focus: AnimFocus, init?: boolean) { + constructor(x: number, y: number, zoomX: number, zoomY: number, angle: number, mirror: boolean, visible: boolean, blendType: AnimBlendType, pattern: number, + opacity: number, colorR: number, colorG: number, colorB: number, colorA: number, toneR: number, toneG: number, toneB: number, toneA: number, + flashR: number, flashG: number, flashB: number, flashA: number, locked: boolean, priority: number, focus: AnimFocus, init?: boolean) { this.x = !init ? ((x || 0) - 128) * 0.5 : x; this.y = !init ? ((y || 0) - 224) * 0.5 : y; if (zoomX) { @@ -288,9 +288,9 @@ class AnimFrame { class ImportedAnimFrame extends AnimFrame { constructor(source: any) { - const color: integer[] = source.color || [ 0, 0, 0, 0 ]; - const tone: integer[] = source.tone || [ 0, 0, 0, 0 ]; - const flash: integer[] = source.flash || [ 0, 0, 0, 0 ]; + const color: number[] = source.color || [ 0, 0, 0, 0 ]; + const tone: number[] = source.tone || [ 0, 0, 0, 0 ]; + const flash: number[] = source.flash || [ 0, 0, 0, 0 ]; super(source.x, source.y, source.zoomX, source.zoomY, source.angle, source.mirror, source.visible, source.blendType, source.graphicFrame, source.opacity, color[0], color[1], color[2], color[3], tone[0], tone[1], tone[2], tone[3], flash[0], flash[1], flash[2], flash[3], source.locked, source.priority, source.focus, true); this.target = source.target; this.graphicFrame = source.graphicFrame; @@ -298,15 +298,15 @@ class ImportedAnimFrame extends AnimFrame { } abstract class AnimTimedEvent { - public frameIndex: integer; + public frameIndex: number; public resourceName: string; - constructor(frameIndex: integer, resourceName: string) { + constructor(frameIndex: number, resourceName: string) { this.frameIndex = frameIndex; this.resourceName = resourceName; } - abstract execute(battleAnim: BattleAnim, priority?: number): integer; + abstract execute(battleAnim: BattleAnim, priority?: number): number; abstract getEventType(): string; } @@ -315,7 +315,7 @@ class AnimTimedSoundEvent extends AnimTimedEvent { public volume: number = 100; public pitch: number = 100; - constructor(frameIndex: integer, resourceName: string, source?: any) { + constructor(frameIndex: number, resourceName: string, source?: any) { super(frameIndex, resourceName); if (source) { @@ -324,7 +324,7 @@ class AnimTimedSoundEvent extends AnimTimedEvent { } } - execute(battleAnim: BattleAnim, priority?: number): integer { + execute(battleAnim: BattleAnim, priority?: number): number { const soundConfig = { rate: (this.pitch * 0.01), volume: (this.volume * 0.01) }; if (this.resourceName) { try { @@ -346,20 +346,20 @@ class AnimTimedSoundEvent extends AnimTimedEvent { abstract class AnimTimedBgEvent extends AnimTimedEvent { public bgX: number = 0; public bgY: number = 0; - public opacity: integer = 0; - /*public colorRed: integer = 0; - public colorGreen: integer = 0; - public colorBlue: integer = 0; - public colorAlpha: integer = 0;*/ - public duration: integer = 0; - /*public flashScope: integer = 0; - public flashRed: integer = 0; - public flashGreen: integer = 0; - public flashBlue: integer = 0; - public flashAlpha: integer = 0; - public flashDuration: integer = 0;*/ + public opacity: number = 0; + /*public colorRed: number = 0; + public colorGreen: number = 0; + public colorBlue: number = 0; + public colorAlpha: number = 0;*/ + public duration: number = 0; + /*public flashScope: number = 0; + public flashRed: number = 0; + public flashGreen: number = 0; + public flashBlue: number = 0; + public flashAlpha: number = 0; + public flashDuration: number = 0;*/ - constructor(frameIndex: integer, resourceName: string, source: any) { + constructor(frameIndex: number, resourceName: string, source: any) { super(frameIndex, resourceName); if (source) { @@ -382,11 +382,11 @@ abstract class AnimTimedBgEvent extends AnimTimedEvent { } class AnimTimedUpdateBgEvent extends AnimTimedBgEvent { - constructor(frameIndex: integer, resourceName: string, source?: any) { + constructor(frameIndex: number, resourceName: string, source?: any) { super(frameIndex, resourceName, source); } - execute(moveAnim: MoveAnim, priority?: number): integer { + execute(moveAnim: MoveAnim, priority?: number): number { const tweenProps = {}; if (this.bgX !== undefined) { tweenProps["x"] = (this.bgX * 0.5) - 320; @@ -412,11 +412,11 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent { } class AnimTimedAddBgEvent extends AnimTimedBgEvent { - constructor(frameIndex: integer, resourceName: string, source?: any) { + constructor(frameIndex: number, resourceName: string, source?: any) { super(frameIndex, resourceName, source); } - execute(moveAnim: MoveAnim, priority?: number): integer { + execute(moveAnim: MoveAnim, priority?: number): number { if (moveAnim.bgSprite) { moveAnim.bgSprite.destroy(); } @@ -737,7 +737,7 @@ function isReversed(src1: number, src2: number, dst1: number, dst2: number) { } interface SpriteCache { - [key: integer]: Phaser.GameObjects.Sprite[] + [key: number]: Phaser.GameObjects.Sprite[] } export abstract class BattleAnim { @@ -774,8 +774,8 @@ export abstract class BattleAnim { return false; } - private getGraphicFrameData(frames: AnimFrame[], onSubstitute?: boolean): Map> { - const ret: Map> = new Map([ + private getGraphicFrameData(frames: AnimFrame[], onSubstitute?: boolean): Map> { + const ret: Map> = new Map([ [ AnimFrameTarget.GRAPHIC, new Map() ], [ AnimFrameTarget.USER, new Map() ], [ AnimFrameTarget.TARGET, new Map() ] @@ -853,7 +853,7 @@ export abstract class BattleAnim { [AnimFrameTarget.USER]: [], [AnimFrameTarget.TARGET]: [] }; - const spritePriorities: integer[] = []; + const spritePriorities: number[] = []; const cleanUpAndComplete = () => { userSprite.setPosition(0, 0); @@ -993,7 +993,7 @@ export abstract class BattleAnim { const moveSprite = sprites[graphicIndex]; if (spritePriorities[graphicIndex] !== frame.priority) { spritePriorities[graphicIndex] = frame.priority; - const setSpritePriority = (priority: integer) => { + const setSpritePriority = (priority: number) => { switch (priority) { case 0: globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, globalScene.getEnemyPokemon(false) ?? globalScene.getPlayerPokemon(false)!); // TODO: is this bang correct? @@ -1093,8 +1093,8 @@ export abstract class BattleAnim { }); } - private getGraphicFrameDataWithoutTarget(frames: AnimFrame[], targetInitialX: number, targetInitialY: number): Map> { - const ret: Map> = new Map([ + private getGraphicFrameDataWithoutTarget(frames: AnimFrame[], targetInitialX: number, targetInitialY: number): Map> { + const ret: Map> = new Map([ [ AnimFrameTarget.GRAPHIC, new Map() ], [ AnimFrameTarget.USER, new Map() ], [ AnimFrameTarget.TARGET, new Map() ] @@ -1188,7 +1188,7 @@ export abstract class BattleAnim { const graphicIndex = graphicFrameCount++; const moveSprite = sprites[graphicIndex]; if (!isNullOrUndefined(frame.priority)) { - const setSpritePriority = (priority: integer) => { + const setSpritePriority = (priority: number) => { if (existingFieldSprites.length > priority) { // Move to specified priority index const index = globalScene.field.getIndex(existingFieldSprites[priority]); diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 4c68de5abc5..c399a9bb595 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -137,7 +137,7 @@ export interface TerrainBattlerTag { * to select restricted moves. */ export abstract class MoveRestrictionBattlerTag extends BattlerTag { - constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: integer, sourceMove?: Moves, sourceId?: integer) { + constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove?: Moves, sourceId?: number) { super(tagType, lapseType, turnCount, sourceMove, sourceId); } @@ -2559,7 +2559,7 @@ export class SubstituteTag extends BattlerTag { /** Is the source Pokemon "in focus," i.e. is it fully visible on the field? */ public sourceInFocus: boolean; - constructor(sourceMove: Moves, sourceId: integer) { + constructor(sourceMove: Moves, sourceId: number) { super(BattlerTagType.SUBSTITUTE, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE, BattlerTagLapseType.HIT ], 0, sourceMove, sourceId, true); } @@ -2871,7 +2871,7 @@ export class SyrupBombTag extends BattlerTag { /** * Telekinesis raises the target into the air for three turns and causes all moves used against the target (aside from OHKO moves) to hit the target unless the target is in a semi-invulnerable state from Fly/Dig. * The first effect is provided by {@linkcode FloatingTag}, the accuracy-bypass effect is provided by TelekinesisTag - * The effects of Telekinesis can be baton passed to a teammate. Unlike the mainline games, Telekinesis can be baton-passed to Mega Gengar. + * The effects of Telekinesis can be baton passed to a teammate. * @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | Moves.TELEKINESIS} */ export class TelekinesisTag extends BattlerTag { diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 2b2048ca18d..10fb9c55d3d 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -115,17 +115,17 @@ export enum MoveSourceType { export abstract class Challenge { public id: Challenges; // The id of the challenge - public value: integer; // The "strength" of the challenge, all challenges have a numerical value. - public maxValue: integer; // The maximum strength of the challenge. - public severity: integer; // The current severity of the challenge. Some challenges have multiple severities in addition to strength. - public maxSeverity: integer; // The maximum severity of the challenge. + public value: number; // The "strength" of the challenge, all challenges have a numerical value. + public maxValue: number; // The maximum strength of the challenge. + public severity: number; // The current severity of the challenge. Some challenges have multiple severities in addition to strength. + public maxSeverity: number; // The maximum severity of the challenge. public conditions: ChallengeCondition[]; /** * @param id {@link Challenges} The enum value for the challenge */ - constructor(id: Challenges, maxValue: integer = Number.MAX_SAFE_INTEGER) { + constructor(id: Challenges, maxValue: number = Number.MAX_SAFE_INTEGER) { this.id = id; this.value = 0; @@ -180,7 +180,7 @@ export abstract class Challenge { /** * Returns the textual representation of a challenge's current value. - * @param overrideValue {@link integer} The value to check for. If undefined, gets the current value. + * @param overrideValue {@link number} The value to check for. If undefined, gets the current value. * @returns {@link string} The localised name for the current value. */ getValue(overrideValue?: number): string { @@ -190,7 +190,7 @@ export abstract class Challenge { /** * Returns the description of a challenge's current value. - * @param overrideValue {@link integer} The value to check for. If undefined, gets the current value. + * @param overrideValue {@link number} The value to check for. If undefined, gets the current value. * @returns {@link string} The localised description for the current value. */ getDescription(overrideValue?: number): string { @@ -257,7 +257,7 @@ export abstract class Challenge { * Gets the "difficulty" value of this challenge. * @returns {@link integer} The difficulty value. */ - getDifficulty(): integer { + getDifficulty(): number { return this.value; } @@ -265,7 +265,7 @@ export abstract class Challenge { * Gets the minimum difficulty added by this challenge. * @returns {@link integer} The difficulty value. */ - getMinDifficulty(): integer { + getMinDifficulty(): number { return 0; } @@ -643,7 +643,7 @@ export class SingleTypeChallenge extends Challenge { * @param {value} overrideValue The value to check for. If undefined, gets the current value. * @returns {string} The localised name for the current value. */ - getValue(overrideValue?: integer): string { + getValue(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } @@ -655,7 +655,7 @@ export class SingleTypeChallenge extends Challenge { * @param {value} overrideValue The value to check for. If undefined, gets the current value. * @returns {string} The localised description for the current value. */ - getDescription(overrideValue?: integer): string { + getDescription(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } @@ -793,7 +793,7 @@ export class LowerStarterMaxCostChallenge extends Challenge { /** * @override */ - getValue(overrideValue?: integer): string { + getValue(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } @@ -827,7 +827,7 @@ export class LowerStarterPointsChallenge extends Challenge { /** * @override */ - getValue(overrideValue?: integer): string { + getValue(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index 2a4a78a9caf..df6c08fc0f0 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -11,7 +11,7 @@ import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { Biome } from "#app/enums/biome"; export interface DailyRunConfig { - seed: integer; + seed: number; starters: Starter; } @@ -38,7 +38,7 @@ export function getDailyRunStarters(seed: string): Starter[] { return; } - const starterCosts: integer[] = []; + const starterCosts: number[] = []; starterCosts.push(Math.min(Math.round(3.5 + Math.abs(Utils.randSeedGauss(1))), 8)); starterCosts.push(Utils.randSeedInt(9 - starterCosts[0], 1)); starterCosts.push(10 - (starterCosts[0] + starterCosts[1])); @@ -57,7 +57,7 @@ export function getDailyRunStarters(seed: string): Starter[] { return starters; } -function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLevel: integer): Starter { +function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLevel: number): Starter { const starterSpecies = starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId); const formIndex = starterSpeciesForm instanceof PokemonSpecies ? undefined : starterSpeciesForm.formIndex; const pokemon = new PlayerPokemon(starterSpecies, startingLevel, undefined, formIndex, undefined, undefined, undefined, undefined, undefined, undefined); @@ -74,7 +74,7 @@ function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLeve } interface BiomeWeights { - [key: integer]: integer + [key: number]: number } // Initially weighted by amount of exits each biome has @@ -125,7 +125,7 @@ export function getDailyStartingBiome(): Biome { const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END); let totalWeight = 0; - const biomeThresholds: integer[] = []; + const biomeThresholds: number[] = []; for (const biome of biomes) { // Keep track of the total weight totalWeight += dailyBiomeWeights[biome]; diff --git a/src/data/dialogue.ts b/src/data/dialogue.ts index b42850fe4de..f4933a070fd 100644 --- a/src/data/dialogue.ts +++ b/src/data/dialogue.ts @@ -9,7 +9,7 @@ export interface TrainerTypeMessages { } export interface TrainerTypeDialogue { - [key: integer]: TrainerTypeMessages | Array + [key: number]: TrainerTypeMessages | Array } export function getTrainerTypeDialogue(): TrainerTypeDialogue { diff --git a/src/data/egg.ts b/src/data/egg.ts index 2599994ecf6..380b5ddabfe 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -373,8 +373,8 @@ export class Egg { } } - let minStarterValue: integer; - let maxStarterValue: integer; + let minStarterValue: number; + let maxStarterValue: number; switch (this.tier) { case EggTier.RARE: diff --git a/src/data/exp.ts b/src/data/exp.ts index c03abddadfc..0f5d3e62cef 100644 --- a/src/data/exp.ts +++ b/src/data/exp.ts @@ -16,7 +16,7 @@ const expLevels = [ [ 0, 4, 13, 32, 65, 112, 178, 276, 393, 540, 745, 967, 1230, 1591, 1957, 2457, 3046, 3732, 4526, 5440, 6482, 7666, 9003, 10506, 12187, 14060, 16140, 18439, 20974, 23760, 26811, 30146, 33780, 37731, 42017, 46656, 50653, 55969, 60505, 66560, 71677, 78533, 84277, 91998, 98415, 107069, 114205, 123863, 131766, 142500, 151222, 163105, 172697, 185807, 196322, 210739, 222231, 238036, 250562, 267840, 281456, 300293, 315059, 335544, 351520, 373744, 390991, 415050, 433631, 459620, 479600, 507617, 529063, 559209, 582187, 614566, 639146, 673863, 700115, 737280, 765275, 804997, 834809, 877201, 908905, 954084, 987754, 1035837, 1071552, 1122660, 1160499, 1214753, 1254796, 1312322, 1354652, 1415577, 1460276, 1524731, 1571884, 1640000 ] ]; -export function getLevelTotalExp(level: integer, growthRate: GrowthRate): integer { +export function getLevelTotalExp(level: number, growthRate: GrowthRate): number { if (level < 100) { const levelExp = expLevels[growthRate][level - 1]; if (growthRate !== GrowthRate.MEDIUM_FAST) { @@ -25,7 +25,7 @@ export function getLevelTotalExp(level: integer, growthRate: GrowthRate): intege return levelExp; } - let ret: integer; + let ret: number; switch (growthRate) { case GrowthRate.ERRATIC: @@ -55,7 +55,7 @@ export function getLevelTotalExp(level: integer, growthRate: GrowthRate): intege return Math.floor(ret); } -export function getLevelRelExp(level: integer, growthRate: GrowthRate): number { +export function getLevelRelExp(level: number, growthRate: GrowthRate): number { return getLevelTotalExp(level, growthRate) - getLevelTotalExp(level - 1, growthRate); } diff --git a/src/data/move.ts b/src/data/move.ts index 9cf9b1a3469..010511ef711 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -44,7 +44,7 @@ import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms"; import type { GameMode } from "#app/game-mode"; import { applyChallenges, ChallengeType } from "./challenge"; import { SwitchType } from "#enums/switch-type"; -import { StatusEffect } from "enums/status-effect"; +import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; export enum MoveCategory { @@ -687,7 +687,7 @@ export default class Move implements Localizable { * @param move {@linkcode Move} using the move * @returns integer representing the total benefitScore */ - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { let score = 0; for (const attr of this.attrs) { @@ -708,7 +708,7 @@ export default class Move implements Localizable { * @param move {@linkcode Move} using the move * @returns integer representing the total benefitScore */ - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { let score = 0; if (target.getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon() === target) { @@ -875,7 +875,7 @@ export default class Move implements Localizable { } export class AttackMove extends Move { - constructor(id: Moves, type: Type, category: MoveCategory, power: integer, accuracy: integer, pp: integer, chance: integer, priority: integer, generation: integer) { + constructor(id: Moves, type: Type, category: MoveCategory, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) { super(id, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, chance, priority, generation); /** @@ -887,7 +887,7 @@ export class AttackMove extends Move { } } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { let ret = super.getTargetBenefitScore(user, target, move); let attackScore = 0; @@ -932,13 +932,13 @@ export class AttackMove extends Move { } export class StatusMove extends Move { - constructor(id: Moves, type: Type, accuracy: integer, pp: integer, chance: integer, priority: integer, generation: integer) { + constructor(id: Moves, type: Type, accuracy: number, pp: number, chance: number, priority: number, generation: number) { super(id, type, MoveCategory.STATUS, MoveTarget.NEAR_OTHER, -1, accuracy, pp, chance, priority, generation); } } export class SelfStatusMove extends Move { - constructor(id: Moves, type: Type, accuracy: integer, pp: integer, chance: integer, priority: integer, generation: integer) { + constructor(id: Moves, type: Type, accuracy: number, pp: number, chance: number, priority: number, generation: number) { super(id, type, MoveCategory.STATUS, MoveTarget.USER, -1, accuracy, pp, chance, priority, generation); } } @@ -1074,7 +1074,7 @@ export abstract class MoveAttr { * @see {@linkcode EnemyPokemon.getNextMove} * @virtual */ - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return 0; } @@ -1083,7 +1083,7 @@ export abstract class MoveAttr { * @see {@linkcode EnemyPokemon.getNextMove} * @virtual */ - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return 0; } } @@ -1201,7 +1201,7 @@ export class MoveEffectAttr extends MoveAttr { * @param selfEffect `true` if move targets user. * @returns Move effect chance value. */ - getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): integer { + getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number { const moveChance = new Utils.NumberHolder(this.effectChanceOverride ?? move.chance); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, moveChance, move, target, selfEffect, showAbility); @@ -1330,7 +1330,7 @@ export class HighCritAttr extends MoveAttr { return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return 3; } } @@ -1342,15 +1342,15 @@ export class CritOnlyAttr extends MoveAttr { return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return 5; } } export class FixedDamageAttr extends MoveAttr { - private damage: integer; + private damage: number; - constructor(damage: integer) { + constructor(damage: number) { super(); this.damage = damage; @@ -1362,7 +1362,7 @@ export class FixedDamageAttr extends MoveAttr { return true; } - getDamage(user: Pokemon, target: Pokemon, move: Move): integer { + getDamage(user: Pokemon, target: Pokemon, move: Move): number { return this.damage; } } @@ -1435,7 +1435,7 @@ export class MatchHpAttr extends FixedDamageAttr { } // TODO - /*getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + /*getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return 0; }*/ } @@ -1446,7 +1446,7 @@ export class CounterDamageAttr extends FixedDamageAttr { private moveFilter: MoveFilter; private multiplier: number; - constructor(moveFilter: MoveFilter, multiplier: integer) { + constructor(moveFilter: MoveFilter, multiplier: number) { super(0); this.moveFilter = moveFilter; @@ -1454,7 +1454,7 @@ export class CounterDamageAttr extends FixedDamageAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const damage = user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).reduce((total: integer, ar: AttackMoveResult) => total + ar.damage, 0); + const damage = user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).reduce((total: number, ar: AttackMoveResult) => total + ar.damage, 0); (args[0] as Utils.IntegerHolder).value = Utils.toDmgValue(damage * this.multiplier); return true; @@ -1493,7 +1493,7 @@ export class ModifiedDamageAttr extends MoveAttr { return true; } - getModifiedDamage(user: Pokemon, target: Pokemon, move: Move, damage: integer): integer { + getModifiedDamage(user: Pokemon, target: Pokemon, move: Move, damage: number): number { return damage; } } @@ -1507,7 +1507,7 @@ export class SurviveDamageAttr extends ModifiedDamageAttr { return (user, target, move) => target.hp > 1; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return target.hp > 1 ? 0 : -20; } } @@ -1563,7 +1563,7 @@ export class RecoilAttr extends MoveEffectAttr { return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return Math.floor((move.power / 5) / -4); } } @@ -1594,7 +1594,7 @@ export class SacrificialAttr extends MoveEffectAttr { return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { if (user.isBoss()) { return -20; } @@ -1632,7 +1632,7 @@ export class SacrificialAttrOnHit extends MoveEffectAttr { return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { if (user.isBoss()) { return -20; } @@ -1674,7 +1674,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr { return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { if (user.isBoss()) { return -10; } @@ -1778,7 +1778,7 @@ export class HealAttr extends MoveEffectAttr { Utils.toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), true, !this.showAnim)); } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { const score = ((1 - (this.selfTarget ? user : target).getHpRatio()) * 20) - this.healRatio * 10; return Math.round(score / (1 - this.healRatio / 2)); } @@ -1870,7 +1870,7 @@ export class FlameBurstAttr extends MoveEffectAttr { return true; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return target.getAlly() ? -5 : 0; } } @@ -1893,7 +1893,7 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr { // We don't know which party member will be chosen, so pick the highest max HP in the party const party = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); - const maxPartyMemberHp = party.map(p => p.getMaxHp()).reduce((maxHp: integer, hp: integer) => Math.max(hp, maxHp), 0); + const maxPartyMemberHp = party.map(p => p.getMaxHp()).reduce((maxHp: number, hp: number) => Math.max(hp, maxHp), 0); globalScene.pushPhase( new PokemonHealPhase( @@ -1911,7 +1911,7 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr { return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return -20; } @@ -2118,7 +2118,7 @@ export class HitHealAttr extends MoveEffectAttr { * @param move {@linkcode Move} being used * @returns an integer. Higher means enemy is more likely to use that move. */ - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { if (this.healStat) { const healAmount = target.getEffectiveStat(this.healStat); return Math.floor(Math.max(0, (Math.min(1, (healAmount + user.hp) / user.getMaxHp() - 0.33))) / user.getHpRatio()); @@ -2138,7 +2138,7 @@ export class IncrementMovePriorityAttr extends MoveAttr { /** The condition for a move's priority being incremented */ private moveIncrementFunc: (pokemon: Pokemon, target:Pokemon, move: Move) => boolean; /** The amount to increment priority by, if condition passes. */ - private increaseAmount: integer; + private increaseAmount: number; constructor(moveIncrementFunc: (pokemon: Pokemon, target:Pokemon, move: Move) => boolean, increaseAmount = 1) { super(); @@ -2222,7 +2222,7 @@ export class MultiHitAttr extends MoveAttr { * @param target {@linkcode Pokemon} targeted by the attack * @returns The number of hits this attack should deal */ - getHitCount(user: Pokemon, target: Pokemon): integer { + getHitCount(user: Pokemon, target: Pokemon): number { switch (this.multiHitType) { case MultiHitType._2_TO_5: { @@ -2669,7 +2669,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr { return this.effects.includes(effect); } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return user.status ? 10 : 0; } } @@ -2690,7 +2690,7 @@ export class BypassSleepAttr extends MoveAttr { * @param target * @param move */ - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return user.status && user.status.effect === StatusEffect.SLEEP ? 200 : -10; } } @@ -3060,11 +3060,11 @@ export class StatStageChangeAttr extends MoveEffectAttr { return false; } - getLevels(_user: Pokemon): integer { + getLevels(_user: Pokemon): number { return this.stages; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { let ret = 0; const moveLevels = this.getLevels(user); for (const stat of this.stats) { @@ -3302,10 +3302,10 @@ export class GrowthStatStageChangeAttr extends StatStageChangeAttr { } export class CutHpStatStageBoostAttr extends StatStageChangeAttr { - private cutRatio: integer; + private cutRatio: number; private messageCallback: ((user: Pokemon) => void) | undefined; - constructor(stat: BattleStat[], levels: integer, cutRatio: integer, messageCallback?: ((user: Pokemon) => void) | undefined) { + constructor(stat: BattleStat[], levels: number, cutRatio: number, messageCallback?: ((user: Pokemon) => void) | undefined) { super(stat, levels, true); this.cutRatio = cutRatio; @@ -3655,7 +3655,7 @@ const doublePowerChanceMessageFunc = (user: Pokemon, target: Pokemon, move: Move export class DoublePowerChanceAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - let rand: integer; + let rand: number; globalScene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), globalScene.currentBattle.turn << 6, globalScene.waveSeed); if (rand! < move.chance) { const power = args[0] as Utils.NumberHolder; @@ -3668,7 +3668,7 @@ export class DoublePowerChanceAttr extends VariablePowerAttr { } export abstract class ConsecutiveUsePowerMultiplierAttr extends MovePowerMultiplierAttr { - constructor(limit: integer, resetOnFail: boolean, resetOnLimit?: boolean, ...comboMoves: Moves[]) { + constructor(limit: number, resetOnFail: boolean, resetOnLimit?: boolean, ...comboMoves: Moves[]) { super((user: Pokemon, target: Pokemon, move: Move): number => { const moveHistory = user.getLastXMoves(limit + 1).slice(1); @@ -3689,7 +3689,7 @@ export abstract class ConsecutiveUsePowerMultiplierAttr extends MovePowerMultipl }); } - abstract getMultiplier(count: integer): number; + abstract getMultiplier(count: number): number; } export class ConsecutiveUseDoublePowerAttr extends ConsecutiveUsePowerMultiplierAttr { @@ -3938,7 +3938,7 @@ export class MagnitudePowerAttr extends VariablePowerAttr { const magnitudeThresholds = [ 5, 15, 35, 65, 75, 95 ]; const magnitudePowers = [ 10, 30, 50, 70, 90, 100, 110, 150 ]; - let rand: integer; + let rand: number; globalScene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), globalScene.currentBattle.turn << 6, globalScene.waveSeed); @@ -4180,9 +4180,9 @@ const hasStockpileStacksCondition: MoveConditionFunc = (user) => { */ export class MultiHitPowerIncrementAttr extends VariablePowerAttr { /** The max number of base power increments allowed for this move */ - private maxHits: integer; + private maxHits: number; - constructor(maxHits: integer) { + constructor(maxHits: number) { super(); this.maxHits = maxHits; @@ -5251,12 +5251,12 @@ export class SemiInvulnerableAttr extends MoveEffectAttr { export class AddBattlerTagAttr extends MoveEffectAttr { public tagType: BattlerTagType; - public turnCountMin: integer; - public turnCountMax: integer; + public turnCountMin: number; + public turnCountMax: number; protected cancelOnFail: boolean; private failOnOverlap: boolean; - constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: integer = 0, turnCountMax?: integer, lastHitOnly: boolean = false, cancelOnFail: boolean = false) { + constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: number = 0, turnCountMax?: number, lastHitOnly: boolean = false, cancelOnFail: boolean = false) { super(selfTarget, { lastHitOnly: lastHitOnly }); this.tagType = tagType; @@ -5293,7 +5293,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr { : null; } - getTagTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer | void { + getTagTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number | void { switch (this.tagType) { case BattlerTagType.RECHARGING: case BattlerTagType.PERISH_SONG: @@ -5340,7 +5340,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr { } } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { let moveChance = this.getMoveChance(user, target, move, this.selfTarget, false); if (moveChance < 0) { moveChance = 100; @@ -5407,7 +5407,7 @@ export class GulpMissileTagAttr extends MoveEffectAttr { return false; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { const isCramorant = user.hasAbility(Abilities.GULP_MISSILE) && user.species.speciesId === Species.CRAMORANT; return isCramorant && !user.getTag(GulpMissileTag) ? 10 : 0; } @@ -5669,7 +5669,7 @@ export class HitsTagAttr extends MoveAttr { this.doubleDamage = !!doubleDamage; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return target.getTag(this.tagType) ? this.doubleDamage ? 10 : 5 : 0; } } @@ -5687,11 +5687,11 @@ export class HitsTagForDoubleDamageAttr extends HitsTagAttr { export class AddArenaTagAttr extends MoveEffectAttr { public tagType: ArenaTagType; - public turnCount: integer; + public turnCount: number; private failOnOverlap: boolean; public selfSideTarget: boolean; - constructor(tagType: ArenaTagType, turnCount?: integer | null, failOnOverlap: boolean = false, selfSideTarget: boolean = false) { + constructor(tagType: ArenaTagType, turnCount?: number | null, failOnOverlap: boolean = false, selfSideTarget: boolean = false) { super(true); this.tagType = tagType; @@ -5983,7 +5983,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr { }); } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { if (user.hasTrainer() && globalScene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) { return 20; } @@ -6212,13 +6212,13 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { }; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { if (!globalScene.getEnemyParty().find(p => p.isActive() && !p.isOnField())) { return -20; } let ret = this.selfSwitch ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move); if (this.selfSwitch && this.isBatonPass()) { - const statStageTotal = user.getStatStages().reduce((s: integer, total: integer) => total += s, 0); + const statStageTotal = user.getStatStages().reduce((s: number, total: number) => total += s, 0); ret = ret / 2 + (Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(statStageTotal), 10) / 10) * (statStageTotal >= 0 ? 10 : -10)); } return ret; @@ -7185,7 +7185,7 @@ export class RepeatMoveAttr extends MoveEffectAttr { }; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { // TODO: Make the AI actually use instruct /* Ideally, the AI would score instruct based on the scorings of the on-field pokemons' * last used moves at the time of using Instruct (by the time the instructor gets to act) @@ -7746,7 +7746,7 @@ export class ShiftStatAttr extends MoveEffectAttr { * @param move n/a * @returns number of points to add to the user's benefit score */ - override getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + override getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return user.getStat(this.statToSwitchWith, false) > user.getStat(this.statToSwitch, false) ? 10 : 0; } } @@ -7798,7 +7798,7 @@ export class AverageStatsAttr extends MoveEffectAttr { } export class DiscourageFrequentUseAttr extends MoveAttr { - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { const lastMoves = user.getLastXMoves(4); console.log(lastMoves); for (let m = 0; m < lastMoves.length; m++) { @@ -8052,7 +8052,7 @@ export class MoveCondition { return this.func(user, target, move); } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return 0; } } @@ -8062,7 +8062,7 @@ export class FirstMoveCondition extends MoveCondition { super((user, target, move) => user.battleSummonData?.waveTurnCount === 1); } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { return this.apply(user, target, move) ? 10 : -20; } } @@ -10429,9 +10429,8 @@ export function initMoves() { new AttackMove(Moves.PIKA_PAPOW, Type.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 20, -1, 0, 7) .attr(FriendshipPowerAttr), new AttackMove(Moves.BOUNCY_BUBBLE, Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, -1, 0, 7) - .attr(HitHealAttr) // Custom - .triageMove() - .target(MoveTarget.ALL_NEAR_ENEMIES), + .attr(HitHealAttr, 1) + .triageMove(), new AttackMove(Moves.BUZZY_BUZZ, Type.ELECTRIC, MoveCategory.SPECIAL, 60, 100, 20, 100, 0, 7) .attr(StatusEffectAttr, StatusEffect.PARALYSIS), new AttackMove(Moves.SIZZLY_SLIDE, Type.FIRE, MoveCategory.PHYSICAL, 60, 100, 20, 100, 0, 7) diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 6dcac277525..88e5794e816 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -148,7 +148,7 @@ export const DancingLessonsEncounter: MysteryEncounter = // Adds a real Pokemon sprite to the field (required for the animation) globalScene.getEnemyParty().forEach(enemyPokemon => { - globalScene.field.remove(enemyPokemon, true); + enemyPokemon.leaveField(true, true, true); }); globalScene.currentBattle.enemyParty = [ oricorio ]; globalScene.field.add(oricorio); diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 4556c3ab6a0..53f89069491 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -229,7 +229,7 @@ function handleLoseMinigame() { // End the battle if (wobbuffet) { wobbuffet.hideInfo(); - globalScene.field.remove(wobbuffet); + wobbuffet.leaveField(); } transitionMysteryEncounterIntroVisuals(true, true); globalScene.currentBattle.enemyParty = []; @@ -278,7 +278,7 @@ function handleNextTurn() { // End the battle wobbuffet.hideInfo(); - globalScene.field.remove(wobbuffet); + wobbuffet.leaveField(); globalScene.currentBattle.enemyParty = []; globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined; leaveEncounterWithoutBattle(isHealPhase); diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index 84768519bea..16015c80fc8 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -168,6 +168,7 @@ async function doBiomeTransitionDialogueAndBattleInit() { // Show dialogue and transition biome await showEncounterText(`${namespace}:transport`); await Promise.all([ animateBiomeChange(newBiome), transitionMysteryEncounterIntroVisuals() ]); + globalScene.updateBiomeWaveText(); globalScene.playBgm(); await showEncounterText(`${namespace}:attacked`); diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 5e87a40d952..2f5843e39d2 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -575,7 +575,7 @@ function onGameOver() { ease: "Sine.easeIn", scale: 0.5, onComplete: () => { - globalScene.field.remove(pokemon, true); + pokemon.leaveField(true, true, true); } }); } diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 8e7c67fae84..351b969b1a8 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -164,7 +164,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): } globalScene.getEnemyParty().forEach(enemyPokemon => { - globalScene.field.remove(enemyPokemon, true); + enemyPokemon.leaveField(true, true, true); }); battle.enemyParty = []; battle.double = doubleBattle; @@ -810,7 +810,7 @@ export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, des globalScene.field.remove(introVisuals, true); enemyPokemon.forEach(pokemon => { - globalScene.field.remove(pokemon, true); + pokemon.leaveField(true, true, true); }); globalScene.currentBattle.mysteryEncounter!.introVisuals = undefined; @@ -887,16 +887,21 @@ export function getRandomEncounterSpecies(level: number, isBoss: boolean = false let bossSpecies: PokemonSpecies; let isEventEncounter = false; const eventEncounters = globalScene.eventManager.getEventEncounters(); + let formIndex; if (eventEncounters.length > 0 && randSeedInt(2) === 1) { const eventEncounter = randSeedItem(eventEncounters); const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, !eventEncounter.blockEvolution, isBoss, globalScene.gameMode); isEventEncounter = true; bossSpecies = getPokemonSpecies(levelSpecies); + formIndex = eventEncounter.formIndex; } else { bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), isBoss); } const ret = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, isBoss); + if (formIndex) { + ret.formIndex = formIndex; + } //Reroll shiny for event encounters if (isEventEncounter && !ret.shiny) { diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index bcce2ad1349..580aaaf2cc6 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -592,7 +592,7 @@ export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameO }; const removePokemon = () => { if (pokemon) { - globalScene.field.remove(pokemon, true); + pokemon.leaveField(false, true, true); } }; const addToParty = (slotIndex?: number) => { @@ -624,7 +624,7 @@ export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameO }); }, false); }, () => { - globalScene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => { + globalScene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: number, _option: PartyOption) => { globalScene.ui.setMode(Mode.MESSAGE).then(() => { if (slotIndex < 6) { addToParty(slotIndex); @@ -695,7 +695,7 @@ export async function doPokemonFlee(pokemon: EnemyPokemon): Promise { scale: pokemon.getSpriteScale(), onComplete: () => { pokemon.setVisible(false); - globalScene.field.remove(pokemon, true); + pokemon.leaveField(true, true, true); showEncounterText(i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false) .then(() => { resolve(); @@ -723,7 +723,7 @@ export function doPlayerFlee(pokemon: EnemyPokemon): Promise { scale: pokemon.getSpriteScale(), onComplete: () => { pokemon.setVisible(false); - globalScene.field.remove(pokemon, true); + pokemon.leaveField(true, true, true); showEncounterText(i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false) .then(() => { resolve(); diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index 049baf11f3d..7cd9f061cdd 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -4,7 +4,7 @@ import { NumberHolder } from "#app/utils"; import { PokeballType } from "#enums/pokeball"; import i18next from "i18next"; -export const MAX_PER_TYPE_POKEBALLS: integer = 99; +export const MAX_PER_TYPE_POKEBALLS: number = 99; export function getPokeballAtlasKey(type: PokeballType): string { switch (type) { diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 035cd6f1369..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 { @@ -755,23 +756,23 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.SHAYMIN, "land", "sky", new SpeciesFormChangeItemTrigger(FormChangeItem.GRACIDEA)), ], [Species.ARCEUS]: [ - new SpeciesFormChange(Species.ARCEUS, "normal", "fighting", new SpeciesFormChangeItemTrigger(FormChangeItem.FIST_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "flying", new SpeciesFormChangeItemTrigger(FormChangeItem.SKY_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "poison", new SpeciesFormChangeItemTrigger(FormChangeItem.TOXIC_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "ground", new SpeciesFormChangeItemTrigger(FormChangeItem.EARTH_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "rock", new SpeciesFormChangeItemTrigger(FormChangeItem.STONE_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "bug", new SpeciesFormChangeItemTrigger(FormChangeItem.INSECT_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "ghost", new SpeciesFormChangeItemTrigger(FormChangeItem.SPOOKY_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "steel", new SpeciesFormChangeItemTrigger(FormChangeItem.IRON_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "fire", new SpeciesFormChangeItemTrigger(FormChangeItem.FLAME_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "water", new SpeciesFormChangeItemTrigger(FormChangeItem.SPLASH_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "grass", new SpeciesFormChangeItemTrigger(FormChangeItem.MEADOW_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "electric", new SpeciesFormChangeItemTrigger(FormChangeItem.ZAP_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "psychic", new SpeciesFormChangeItemTrigger(FormChangeItem.MIND_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "ice", new SpeciesFormChangeItemTrigger(FormChangeItem.ICICLE_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "dragon", new SpeciesFormChangeItemTrigger(FormChangeItem.DRACO_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "dark", new SpeciesFormChangeItemTrigger(FormChangeItem.DREAD_PLATE)), - new SpeciesFormChange(Species.ARCEUS, "normal", "fairy", new SpeciesFormChangeItemTrigger(FormChangeItem.PIXIE_PLATE)) + new SpeciesFormChange(Species.ARCEUS, "normal", "fighting", new SpeciesFormChangeItemTrigger(FormChangeItem.FIST_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "flying", new SpeciesFormChangeItemTrigger(FormChangeItem.SKY_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "poison", new SpeciesFormChangeItemTrigger(FormChangeItem.TOXIC_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "ground", new SpeciesFormChangeItemTrigger(FormChangeItem.EARTH_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "rock", new SpeciesFormChangeItemTrigger(FormChangeItem.STONE_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "bug", new SpeciesFormChangeItemTrigger(FormChangeItem.INSECT_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "ghost", new SpeciesFormChangeItemTrigger(FormChangeItem.SPOOKY_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "steel", new SpeciesFormChangeItemTrigger(FormChangeItem.IRON_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "fire", new SpeciesFormChangeItemTrigger(FormChangeItem.FLAME_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "water", new SpeciesFormChangeItemTrigger(FormChangeItem.SPLASH_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "grass", new SpeciesFormChangeItemTrigger(FormChangeItem.MEADOW_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "electric", new SpeciesFormChangeItemTrigger(FormChangeItem.ZAP_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "psychic", new SpeciesFormChangeItemTrigger(FormChangeItem.MIND_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "ice", new SpeciesFormChangeItemTrigger(FormChangeItem.ICICLE_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "dragon", new SpeciesFormChangeItemTrigger(FormChangeItem.DRACO_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "dark", new SpeciesFormChangeItemTrigger(FormChangeItem.DREAD_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), + new SpeciesFormChange(Species.ARCEUS, "normal", "fairy", new SpeciesFormChangeItemTrigger(FormChangeItem.PIXIE_PLATE), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.MULTITYPE))), ], [Species.DARMANITAN]: [ new SpeciesFormChange(Species.DARMANITAN, "", "zen", new SpeciesFormChangeAbilityTrigger(), true), @@ -841,23 +842,23 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.WISHIWASHI, "school", "", new SpeciesFormChangeAbilityTrigger(), true) ], [Species.SILVALLY]: [ - new SpeciesFormChange(Species.SILVALLY, "normal", "fighting", new SpeciesFormChangeItemTrigger(FormChangeItem.FIGHTING_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "flying", new SpeciesFormChangeItemTrigger(FormChangeItem.FLYING_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "poison", new SpeciesFormChangeItemTrigger(FormChangeItem.POISON_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "ground", new SpeciesFormChangeItemTrigger(FormChangeItem.GROUND_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "rock", new SpeciesFormChangeItemTrigger(FormChangeItem.ROCK_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "bug", new SpeciesFormChangeItemTrigger(FormChangeItem.BUG_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "ghost", new SpeciesFormChangeItemTrigger(FormChangeItem.GHOST_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "steel", new SpeciesFormChangeItemTrigger(FormChangeItem.STEEL_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "fire", new SpeciesFormChangeItemTrigger(FormChangeItem.FIRE_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "water", new SpeciesFormChangeItemTrigger(FormChangeItem.WATER_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "grass", new SpeciesFormChangeItemTrigger(FormChangeItem.GRASS_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "electric", new SpeciesFormChangeItemTrigger(FormChangeItem.ELECTRIC_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "psychic", new SpeciesFormChangeItemTrigger(FormChangeItem.PSYCHIC_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "ice", new SpeciesFormChangeItemTrigger(FormChangeItem.ICE_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "dragon", new SpeciesFormChangeItemTrigger(FormChangeItem.DRAGON_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "dark", new SpeciesFormChangeItemTrigger(FormChangeItem.DARK_MEMORY)), - new SpeciesFormChange(Species.SILVALLY, "normal", "fairy", new SpeciesFormChangeItemTrigger(FormChangeItem.FAIRY_MEMORY)) + new SpeciesFormChange(Species.SILVALLY, "normal", "fighting", new SpeciesFormChangeItemTrigger(FormChangeItem.FIGHTING_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "flying", new SpeciesFormChangeItemTrigger(FormChangeItem.FLYING_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "poison", new SpeciesFormChangeItemTrigger(FormChangeItem.POISON_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "ground", new SpeciesFormChangeItemTrigger(FormChangeItem.GROUND_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "rock", new SpeciesFormChangeItemTrigger(FormChangeItem.ROCK_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "bug", new SpeciesFormChangeItemTrigger(FormChangeItem.BUG_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "ghost", new SpeciesFormChangeItemTrigger(FormChangeItem.GHOST_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "steel", new SpeciesFormChangeItemTrigger(FormChangeItem.STEEL_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "fire", new SpeciesFormChangeItemTrigger(FormChangeItem.FIRE_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "water", new SpeciesFormChangeItemTrigger(FormChangeItem.WATER_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "grass", new SpeciesFormChangeItemTrigger(FormChangeItem.GRASS_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "electric", new SpeciesFormChangeItemTrigger(FormChangeItem.ELECTRIC_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "psychic", new SpeciesFormChangeItemTrigger(FormChangeItem.PSYCHIC_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "ice", new SpeciesFormChangeItemTrigger(FormChangeItem.ICE_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "dragon", new SpeciesFormChangeItemTrigger(FormChangeItem.DRAGON_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "dark", new SpeciesFormChangeItemTrigger(FormChangeItem.DARK_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))), + new SpeciesFormChange(Species.SILVALLY, "normal", "fairy", new SpeciesFormChangeItemTrigger(FormChangeItem.FAIRY_MEMORY), true, new SpeciesFormChangeCondition((p) => p.hasAbility(Abilities.RKS_SYSTEM))) ], [Species.MINIOR]: [ new SpeciesFormChange(Species.MINIOR, "red-meteor", "red", new SpeciesFormChangeAbilityTrigger(), true), diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 6abdfe43470..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) { @@ -718,7 +718,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali * The calculation with evolution delay is a weighted average of the easeIn and easeOut functions where preferredMinLevel is the denominator. * This also means a lower value of x will lead to a higher evolution chance. * @param strength {@linkcode PartyMemberStrength} The strength of the party member in question - * @returns {@linkcode integer} The level difference from expected evolution level tolerated for a mon to be unevolved. Lower value = higher evolution chance. + * @returns {@linkcode number} The level difference from expected evolution level tolerated for a mon to be unevolved. Lower value = higher evolution chance. */ private getStrengthLevelDiff(strength: PartyMemberStrength): number { switch (Math.min(strength, PartyMemberStrength.STRONGER)) { @@ -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/data/terrain.ts b/src/data/terrain.ts index da315a14a86..1ffe0adc8bf 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -15,9 +15,9 @@ export enum TerrainType { export class Terrain { public terrainType: TerrainType; - public turnsLeft: integer; + public turnsLeft: number; - constructor(terrainType: TerrainType, turnsLeft?: integer) { + constructor(terrainType: TerrainType, turnsLeft?: number) { this.terrainType = terrainType; this.turnsLeft = turnsLeft || 0; } @@ -81,7 +81,7 @@ export function getTerrainName(terrainType: TerrainType): string { } -export function getTerrainColor(terrainType: TerrainType): [ integer, integer, integer ] { +export function getTerrainColor(terrainType: TerrainType): [ number, number, number ] { switch (terrainType) { case TerrainType.MISTY: return [ 232, 136, 200 ]; diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index 590a8688428..016a2c9bca6 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -35,7 +35,7 @@ export enum TrainerPoolTier { } export interface TrainerTierPools { - [key: integer]: Species[] + [key: number]: Species[] } export enum TrainerSlot { @@ -45,27 +45,27 @@ export enum TrainerSlot { } export class TrainerPartyTemplate { - public size: integer; + public size: number; public strength: PartyMemberStrength; public sameSpecies: boolean; public balanced: boolean; - constructor(size: integer, strength: PartyMemberStrength, sameSpecies?: boolean, balanced?: boolean) { + constructor(size: number, strength: PartyMemberStrength, sameSpecies?: boolean, balanced?: boolean) { this.size = size; this.strength = strength; this.sameSpecies = !!sameSpecies; this.balanced = !!balanced; } - getStrength(index: integer): PartyMemberStrength { + getStrength(index: number): PartyMemberStrength { return this.strength; } - isSameSpecies(index: integer): boolean { + isSameSpecies(index: number): boolean { return this.sameSpecies; } - isBalanced(index: integer): boolean { + isBalanced(index: number): boolean { return this.balanced; } } @@ -74,14 +74,14 @@ export class TrainerPartyCompoundTemplate extends TrainerPartyTemplate { public templates: TrainerPartyTemplate[]; constructor(...templates: TrainerPartyTemplate[]) { - super(templates.reduce((total: integer, template: TrainerPartyTemplate) => { + super(templates.reduce((total: number, template: TrainerPartyTemplate) => { total += template.size; return total; }, 0), PartyMemberStrength.AVERAGE); this.templates = templates; } - getStrength(index: integer): PartyMemberStrength { + getStrength(index: number): PartyMemberStrength { let t = 0; for (const template of this.templates) { if (t + template.size > index) { @@ -93,7 +93,7 @@ export class TrainerPartyCompoundTemplate extends TrainerPartyTemplate { return super.getStrength(index); } - isSameSpecies(index: integer): boolean { + isSameSpecies(index: number): boolean { let t = 0; for (const template of this.templates) { if (t + template.size > index) { @@ -105,7 +105,7 @@ export class TrainerPartyCompoundTemplate extends TrainerPartyTemplate { return super.isSameSpecies(index); } - isBalanced(index: integer): boolean { + isBalanced(index: number): boolean { let t = 0; for (const template of this.templates) { if (t + template.size > index) { @@ -173,11 +173,11 @@ export const trainerPartyTemplates = { }; type PartyTemplateFunc = () => TrainerPartyTemplate; -type PartyMemberFunc = (level: integer, strength: PartyMemberStrength) => EnemyPokemon; +type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon; type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[]; export interface PartyMemberFuncs { - [key: integer]: PartyMemberFunc + [key: number]: PartyMemberFunc } export class TrainerConfig { @@ -524,7 +524,7 @@ export class TrainerConfig { return this; } - setPartyMemberFunc(slotIndex: integer, partyMemberFunc: PartyMemberFunc): TrainerConfig { + setPartyMemberFunc(slotIndex: number, partyMemberFunc: PartyMemberFunc): TrainerConfig { this.partyMemberFuncs[slotIndex] = partyMemberFunc; return this; } @@ -1125,7 +1125,7 @@ export class TrainerConfig { let t = 0; interface TrainerConfigs { - [key: integer]: TrainerConfig + [key: number]: TrainerConfig } /** @@ -1197,7 +1197,7 @@ function getSpeciesFilterRandomPartyMemberFunc( }; } -function getRandomTeraModifiers(party: EnemyPokemon[], count: integer, types?: Type[]): PersistentModifier[] { +function getRandomTeraModifiers(party: EnemyPokemon[], count: number, types?: Type[]): PersistentModifier[] { const ret: PersistentModifier[] = []; const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i); for (let t = 0; t < Math.min(count, party.length); t++) { diff --git a/src/data/trainer-names.ts b/src/data/trainer-names.ts index d075b7121f2..8b0091d4398 100644 --- a/src/data/trainer-names.ts +++ b/src/data/trainer-names.ts @@ -16,7 +16,7 @@ class TrainerNameConfig { } interface TrainerNameConfigs { - [key: integer]: TrainerNameConfig + [key: number]: TrainerNameConfig } // used in a commented code diff --git a/src/data/type.ts b/src/data/type.ts index 6170eadc91e..498394bf90e 100644 --- a/src/data/type.ts +++ b/src/data/type.ts @@ -313,7 +313,7 @@ export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, s } } -export function getTypeRgb(type: Type): [ integer, integer, integer ] { +export function getTypeRgb(type: Type): [ number, number, number ] { switch (type) { case Type.NORMAL: return [ 168, 168, 120 ]; diff --git a/src/data/variant.ts b/src/data/variant.ts index 13869635f1e..13c11b0bb40 100644 --- a/src/data/variant.ts +++ b/src/data/variant.ts @@ -8,7 +8,7 @@ export const variantData: any = {}; export const variantColorCache = {}; -export function getVariantTint(variant: Variant): integer { +export function getVariantTint(variant: Variant): number { switch (variant) { case 0: return 0xf8c020; @@ -19,7 +19,7 @@ export function getVariantTint(variant: Variant): integer { } } -export function getVariantIcon(variant: Variant): integer { +export function getVariantIcon(variant: Variant): number { switch (variant) { case 0: return VariantTier.STANDARD; diff --git a/src/data/weather.ts b/src/data/weather.ts index 7bba698f9b4..0c90f381130 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -14,9 +14,9 @@ import type { Arena } from "#app/field/arena"; export class Weather { public weatherType: WeatherType; - public turnsLeft: integer; + public turnsLeft: number; - constructor(weatherType: WeatherType, turnsLeft?: integer) { + constructor(weatherType: WeatherType, turnsLeft?: number) { this.weatherType = weatherType; this.turnsLeft = !this.isImmutable() ? turnsLeft || 0 : 0; } @@ -246,7 +246,7 @@ export function getTerrainBlockMessage(pokemon: Pokemon, terrainType: TerrainTyp export interface WeatherPoolEntry { weatherType: WeatherType; - weight: integer; + weight: number; } export function getRandomWeatherType(arena: Arena): WeatherType { diff --git a/src/events/egg.ts b/src/events/egg.ts index b30c37a25e8..dc3d2b55ffe 100644 --- a/src/events/egg.ts +++ b/src/events/egg.ts @@ -12,7 +12,7 @@ export enum EggEventType { */ export class EggCountChangedEvent extends Event { /** The updated egg count. */ - public eggCount: integer; + public eggCount: number; constructor(eggCount: number) { super(EggEventType.EGG_COUNT_CHANGED); diff --git a/src/field/anims.ts b/src/field/anims.ts index f96427326b5..9ffaae59bbc 100644 --- a/src/field/anims.ts +++ b/src/field/anims.ts @@ -34,7 +34,7 @@ function doDefaultPbOpenParticles(x: number, y: number, radius: number) { }); } - const addParticle = (index: integer) => { + const addParticle = (index: number) => { const particle = globalScene.add.sprite(x, y, "pb_open_particle"); globalScene.field.add(particle); const angle = index * 45; @@ -68,7 +68,7 @@ function doDefaultPbOpenParticles(x: number, y: number, radius: number) { }); } -function doUbOpenParticles(x: number, y: number, frameIndex: integer) { +function doUbOpenParticles(x: number, y: number, frameIndex: number) { const particles: Phaser.GameObjects.Image[] = []; for (let i = 0; i < 10; i++) { particles.push(doFanOutParticle(i * 25, x, y, 1, 1, 5, frameIndex)); @@ -110,7 +110,7 @@ function doMbOpenParticles(x: number, y: number) { } } -function doFanOutParticle(trigIndex: integer, x: integer, y: integer, xSpeed: integer, ySpeed: integer, angle: integer, frameIndex: integer): Phaser.GameObjects.Image { +function doFanOutParticle(trigIndex: number, x: number, y: number, xSpeed: number, ySpeed: number, angle: number, frameIndex: number): Phaser.GameObjects.Image { let f = 0; const particle = globalScene.add.image(x, y, "pb_particles", `${frameIndex}.png`); @@ -179,11 +179,11 @@ export function addPokeballCaptureStars(pokeball: Phaser.GameObjects.Sprite): vo new Array(3).fill(null).map(() => addParticle()); } -export function sin(index: integer, amplitude: integer): number { +export function sin(index: number, amplitude: number): number { return amplitude * Math.sin(index * (Math.PI / 128)); } -export function cos(index: integer, amplitude: integer): number { +export function cos(index: number, amplitude: number): number { return amplitude * Math.cos(index * (Math.PI / 128)); } diff --git a/src/field/arena.ts b/src/field/arena.ts index deeb261a491..67b83e9518f 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -86,7 +86,7 @@ export class Arena { } } - randomSpecies(waveIndex: integer, level: integer, attempt?: integer, luckValue?: integer, isBoss?: boolean): PokemonSpecies { + randomSpecies(waveIndex: number, level: number, attempt?: number, luckValue?: number, isBoss?: boolean): PokemonSpecies { const overrideSpecies = globalScene.gameMode.getOverrideSpecies(waveIndex); if (overrideSpecies) { return overrideSpecies; @@ -167,7 +167,7 @@ export class Arena { return ret; } - randomTrainerType(waveIndex: integer, isBoss: boolean = false): TrainerType { + randomTrainerType(waveIndex: number, isBoss: boolean = false): TrainerType { const isTrainerBoss = !!this.trainerPool[BiomePoolTier.BOSS].length && (globalScene.gameMode.isTrainerBoss(waveIndex, this.biomeType, globalScene.offsetGym) || isBoss); console.log(isBoss, this.trainerPool); @@ -184,7 +184,7 @@ export class Arena { return !tierPool.length ? TrainerType.BREEDER : tierPool[Utils.randSeedInt(tierPool.length)]; } - getSpeciesFormIndex(species: PokemonSpecies): integer { + getSpeciesFormIndex(species: PokemonSpecies): number { switch (species.speciesId) { case Species.BURMY: case Species.WORMADAM: @@ -372,7 +372,7 @@ export class Arena { * Gets the denominator for the chance for a trainer spawn * @returns n where 1/n is the chance of a trainer battle */ - getTrainerChance(): integer { + getTrainerChance(): number { switch (this.biomeType) { case Biome.METROPOLIS: return 2; @@ -457,10 +457,10 @@ export class Arena { } } - overrideTint(): [integer, integer, integer] { + overrideTint(): [number, number, number] { switch (Overrides.ARENA_TINT_OVERRIDE) { case TimeOfDay.DUSK: - return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; + return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [number, number, number]; break; case (TimeOfDay.NIGHT): return [ 64, 64, 64 ]; @@ -473,7 +473,7 @@ export class Arena { } } - getDayTint(): [integer, integer, integer] { + getDayTint(): [number, number, number] { if (Overrides.ARENA_TINT_OVERRIDE !== null) { return this.overrideTint(); } @@ -485,7 +485,7 @@ export class Arena { } } - getDuskTint(): [integer, integer, integer] { + getDuskTint(): [number, number, number] { if (Overrides.ARENA_TINT_OVERRIDE) { return this.overrideTint(); } @@ -495,11 +495,11 @@ export class Arena { switch (this.biomeType) { default: - return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; + return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [number, number, number]; } } - getNightTint(): [integer, integer, integer] { + getNightTint(): [number, number, number] { if (Overrides.ARENA_TINT_OVERRIDE) { return this.overrideTint(); } @@ -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; @@ -813,7 +815,7 @@ export function getBiomeHasProps(biomeType: Biome): boolean { export class ArenaBase extends Phaser.GameObjects.Container { public player: boolean; public biome: Biome; - public propValue: integer; + public propValue: number; public base: Phaser.GameObjects.Sprite; public props: Phaser.GameObjects.Sprite[]; @@ -834,7 +836,7 @@ export class ArenaBase extends Phaser.GameObjects.Container { }) : []; } - setBiome(biome: Biome, propValue?: integer): void { + setBiome(biome: Biome, propValue?: number): void { const hasProps = getBiomeHasProps(biome); const biomeKey = getBiomeKey(biome); const baseKey = `${biomeKey}_${this.player ? "a" : "b"}`; diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index 57b9d6990ca..1551edc9697 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -15,7 +15,7 @@ export default class DamageNumberHandler { this.damageNumbers = new Map(); } - add(target: Pokemon, amount: integer, result: DamageResult | HitResult.HEAL = HitResult.EFFECTIVE, critical: boolean = false): void { + add(target: Pokemon, amount: number, result: DamageResult | HitResult.HEAL = HitResult.EFFECTIVE, critical: boolean = false): void { if (!globalScene?.damageNumbersMode) { return; } diff --git a/src/field/mystery-encounter-intro.ts b/src/field/mystery-encounter-intro.ts index 0110dabc7a9..1ea8f16e8f7 100644 --- a/src/field/mystery-encounter-intro.ts +++ b/src/field/mystery-encounter-intro.ts @@ -422,7 +422,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con * @param duration * @param ease */ - private tint(sprite, color: number, alpha?: number, duration?: integer, ease?: string): void { + private tint(sprite, color: number, alpha?: number, duration?: number, ease?: string): void { // const tintSprites = this.getTintSprites(); sprite.setTintFill(color); sprite.setVisible(true); @@ -448,7 +448,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con * @param duration * @param ease */ - tintAll(color: number, alpha?: number, duration?: integer, ease?: string): void { + tintAll(color: number, alpha?: number, duration?: number, ease?: string): void { const tintSprites = this.getTintSprites(); tintSprites.map(tintSprite => { this.tint(tintSprite, color, alpha, duration, ease); @@ -461,7 +461,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con * @param duration * @param ease */ - private untint(sprite, duration: integer, ease?: string): void { + private untint(sprite, duration: number, ease?: string): void { if (duration) { globalScene.tweens.add({ targets: sprite, @@ -485,7 +485,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con * @param duration * @param ease */ - untintAll(duration: integer, ease?: string): void { + untintAll(duration: number, ease?: string): void { const tintSprites = this.getTintSprites(); tintSprites.map(tintSprite => { this.untint(tintSprite, duration, ease); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e18026d832d..0ffa2d7399b 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -31,7 +31,7 @@ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoo import { WeatherType } from "#enums/weather-type"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import type { Ability, AbAttr } from "#app/data/ability"; -import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr, applyMidTurnAbAttrs, applyMidTurnClearWeatherAbAttrs } from "#app/data/ability"; +import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr, applyMidTurnAbAttrs, applyMidTurnClearWeatherAbAttrs, PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs } from "#app/data/ability"; import type PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#app/battle"; import { Mode } from "#app/ui/ui"; @@ -99,45 +99,45 @@ export enum FieldPosition { } export default abstract class Pokemon extends Phaser.GameObjects.Container { - public id: integer; + public id: number; public name: string; public nickname: string; public species: PokemonSpecies; - public formIndex: integer; - public abilityIndex: integer; + public formIndex: number; + public abilityIndex: number; public passive: boolean; public shiny: boolean; public variant: Variant; public pokeball: PokeballType; protected battleInfo: BattleInfo; - public level: integer; - public exp: integer; - public levelExp: integer; + public level: number; + public exp: number; + public levelExp: number; public gender: Gender; - public hp: integer; - public stats: integer[]; - public ivs: integer[]; + public hp: number; + public stats: number[]; + public ivs: number[]; public nature: Nature; public moveset: (PokemonMove | null)[]; public status: Status | null; - public friendship: integer; - public metLevel: integer; + public friendship: number; + public metLevel: number; public metBiome: Biome | -1; public metSpecies: Species; public metWave: number; - public luck: integer; + public luck: number; public pauseEvolutions: boolean; public pokerus: boolean; public switchOutStatus: boolean; - public evoCounter: integer; + public evoCounter: number; public fusionSpecies: PokemonSpecies | null; - public fusionFormIndex: integer; - public fusionAbilityIndex: integer; + public fusionFormIndex: number; + public fusionAbilityIndex: number; public fusionShiny: boolean; public fusionVariant: Variant; public fusionGender: Gender; - public fusionLuck: integer; + public fusionLuck: number; public fusionCustomPokemonData: CustomPokemonData | null; private summonDataPrimer: PokemonSummonData | null; @@ -160,7 +160,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { private shinySparkle: Phaser.GameObjects.Sprite; - constructor(x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { + constructor(x: number, y: number, species: PokemonSpecies, level: number, abilityIndex?: number, formIndex?: number, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: number[], nature?: Nature, dataSource?: Pokemon | PokemonData) { super(globalScene, x, y); if (!species.isObtainable() && this.isPlayer()) { @@ -423,7 +423,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { abstract hasTrainer(): boolean; - abstract getFieldIndex(): integer; + abstract getFieldIndex(): number; abstract getBattlerIndex(): BattlerIndex; @@ -781,7 +781,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - setFieldPosition(fieldPosition: FieldPosition, duration?: integer): Promise { + setFieldPosition(fieldPosition: FieldPosition, duration?: number): Promise { return new Promise(resolve => { if (fieldPosition === this.fieldPosition) { resolve(); @@ -949,7 +949,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param simulated if `true`, nullifies any effects that produce any changes to game state from triggering * @returns the final in-battle value of a stat */ - getEffectiveStat(stat: EffectiveStat, opponent?: Pokemon, move?: Move, ignoreAbility: boolean = false, ignoreOppAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): integer { + getEffectiveStat(stat: EffectiveStat, opponent?: Pokemon, move?: Move, ignoreAbility: boolean = false, ignoreOppAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): number { const statValue = new Utils.NumberHolder(this.getStat(stat, false)); globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue); @@ -1103,12 +1103,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.hp >= this.getMaxHp(); } - getMaxHp(): integer { + getMaxHp(): number { return this.getStat(Stat.HP); } /** Returns the amount of hp currently missing from this {@linkcode Pokemon} (max - current) */ - getInverseHp(): integer { + getInverseHp(): number { return this.getMaxHp() - this.hp; } @@ -1153,7 +1153,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return !this.isFusion() ? this.variant : Math.max(this.variant, this.fusionVariant) as Variant; } - getLuck(): integer { + getLuck(): number { return this.luck + (this.isFusion() ? this.fusionLuck : 0); } @@ -1434,8 +1434,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public hasPassive(): boolean { // returns override if valid for current case - if ((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && this.isPlayer()) - || (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && !this.isPlayer())) { + if ( + (Overrides.HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isPlayer()) + || (Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE === false && !this.isPlayer()) + ) { + return false; + } + if ( + ((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) && this.isPlayer()) + || ((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) && !this.isPlayer()) + ) { return true; } @@ -1828,13 +1836,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * Gets all level up moves in a given range for a particular pokemon. - * @param {integer} startingLevel Don't include moves below this level + * @param {number} startingLevel Don't include moves below this level * @param {boolean} includeEvolutionMoves Whether to include evolution moves * @param {boolean} simulateEvolutionChain Whether to include moves from prior evolutions * @param {boolean} includeRelearnerMoves Whether to include moves that would require a relearner. Note the move relearner inherently allows evolution moves * @returns {LevelMoves} A list of moves and the levels they can be learned at */ - getLevelMoves(startingLevel?: integer, includeEvolutionMoves: boolean = false, simulateEvolutionChain: boolean = false, includeRelearnerMoves: boolean = false, learnSituation: LearnMoveSituation = LearnMoveSituation.MISC): LevelMoves { + getLevelMoves(startingLevel?: number, includeEvolutionMoves: boolean = false, simulateEvolutionChain: boolean = false, includeRelearnerMoves: boolean = false, learnSituation: LearnMoveSituation = LearnMoveSituation.MISC): LevelMoves { const ret: LevelMoves = []; let levelMoves: LevelMoves = []; if (!startingLevel) { @@ -1874,7 +1882,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } } - levelMoves.sort((lma: [integer, integer], lmb: [integer, integer]) => lma[0] > lmb[0] ? 1 : lma[0] < lmb[0] ? -1 : 0); + levelMoves.sort((lma: [number, number], lmb: [number, number]) => lma[0] > lmb[0] ? 1 : lma[0] < lmb[0] ? -1 : 0); /** @@ -1931,7 +1939,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return speciesEggMoves[this.getSpeciesForm().getRootSpeciesId()]; } - setMove(moveIndex: integer, moveId: Moves): void { + setMove(moveIndex: number, moveId: Moves): void { const move = moveId ? new PokemonMove(moveId) : null; this.moveset[moveIndex] = move; if (this.summonData?.moveset) { @@ -2338,7 +2346,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - public trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean { + public trySelectMove(moveIndex: number, ignorePp?: boolean): boolean { const move = this.getMoveset().length > moveIndex ? this.getMoveset()[moveIndex] : null; @@ -2424,7 +2432,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param exp The amount of experience to add * @param ignoreLevelCap Whether to ignore level caps when adding experience (defaults to false) */ - addExp(exp: integer, ignoreLevelCap: boolean = false) { + addExp(exp: number, ignoreLevelCap: boolean = false) { const maxExpLevel = globalScene.getMaxExpLevel(ignoreLevelCap); const initialExp = this.exp; this.exp += exp; @@ -2447,7 +2455,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.isPlayer() !== target.isPlayer(); } - getOpponent(targetIndex: integer): Pokemon | null { + getOpponent(targetIndex: number): Pokemon | null { const ret = this.getOpponents()[targetIndex]; if (ret.summonData) { return ret; @@ -3004,7 +3012,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreFaintPhase flag on wheter to add FaintPhase if pokemon after applying damage faints * @returns integer representing damage */ - damage(damage: integer, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false): integer { + damage(damage: number, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false): number { if (this.isFainted()) { return 0; } @@ -3074,7 +3082,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return damage; } - heal(amount: integer): integer { + heal(amount: number): number { const healAmount = Math.min(amount, this.getMaxHp() - this.hp); this.hp += healAmount; return healAmount; @@ -3105,7 +3113,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return !cancelled.value; } - addTag(tagType: BattlerTagType, turnCount: integer = 0, sourceMove?: Moves, sourceId?: integer): boolean { + addTag(tagType: BattlerTagType, turnCount: number = 0, sourceMove?: Moves, sourceId?: number): boolean { const existingTag = this.getTag(tagType); if (existingTag) { existingTag.onOverlap(this); @@ -3211,11 +3219,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return true; } - removeTagsBySourceId(sourceId: integer): void { + removeTagsBySourceId(sourceId: number): void { this.findAndRemoveTags(t => t.isSourceLinked() && t.sourceId === sourceId); } - transferTagsBySourceId(sourceId: integer, newSourceId: integer): void { + transferTagsBySourceId(sourceId: number, newSourceId: number): void { if (!this.summonData) { return; } @@ -3238,7 +3246,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } for (const tag of source.summonData.tags) { - if (!tag.isBatonPassable) { + if (!tag.isBatonPassable || (tag.tagType === BattlerTagType.TELEKINESIS && this.species.speciesId === Species.GENGAR && this.getFormKey() === "mega")) { continue; } @@ -3792,12 +3800,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.turnData = new PokemonTurnData(); } - getExpValue(): integer { + getExpValue(): number { // Logic to factor in victor level has been removed for balancing purposes, so the player doesn't have to focus on EXP maxxing return ((this.getSpeciesForm().getBaseExp() * this.level) / 5 + 1); } - setFrameRate(frameRate: integer) { + setFrameRate(frameRate: number) { globalScene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate; try { this.getSprite().play(this.getBattleSpriteKey()); @@ -3811,7 +3819,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - tint(color: number, alpha?: number, duration?: integer, ease?: string) { + tint(color: number, alpha?: number, duration?: number, ease?: string) { const tintSprite = this.getTintSprite(); tintSprite?.setTintFill(color); tintSprite?.setVisible(true); @@ -3830,7 +3838,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - untint(duration: integer, ease?: string) { + untint(duration: number, ease?: string) { const tintSprite = this.getTintSprite(); if (duration) { @@ -3907,10 +3915,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const fusionCanvas = document.createElement("canvas"); const fusionBackCanvas = document.createElement("canvas"); - const spriteColors: integer[][] = []; + const spriteColors: number[][] = []; const pixelData: Uint8ClampedArray[] = []; - [ canvas, backCanvas, fusionCanvas, fusionBackCanvas ].forEach((canv: HTMLCanvasElement, c: integer) => { + [ canvas, backCanvas, fusionCanvas, fusionBackCanvas ].forEach((canv: HTMLCanvasElement, c: number) => { const context = canv.getContext("2d"); const frame = [ sourceFrame, sourceBackFrame, fusionFrame, fusionBackFrame ][c]; canv.width = frame.width; @@ -3925,7 +3933,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { for (let f = 0; f < 2; f++) { const variantColors = variantColorCache[!f ? spriteKey : backSpriteKey]; - const variantColorSet = new Map(); + const variantColorSet = new Map(); if (this.shiny && variantColors && variantColors[this.variant]) { Object.keys(variantColors[this.variant]).forEach(k => { variantColorSet.set(Utils.rgbaToInt(Array.from(Object.values(Utils.rgbHexToRgba(k)))), Array.from(Object.values(Utils.rgbHexToRgba(variantColors[this.variant][k])))); @@ -3957,7 +3965,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const pixelColors: number[] = []; for (let f = 0; f < 2; f++) { for (let i = 0; i < pixelData[f].length; i += 4) { - const total = pixelData[f].slice(i, i + 3).reduce((total: integer, value: integer) => total + value, 0); + const total = pixelData[f].slice(i, i + 3).reduce((total: number, value: number) => total + value, 0); if (!total) { continue; } @@ -3968,14 +3976,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const fusionPixelColors : number[] = []; for (let f = 0; f < 2; f++) { const variantColors = variantColorCache[!f ? fusionSpriteKey : fusionBackSpriteKey]; - const variantColorSet = new Map(); + const variantColorSet = new Map(); if (this.fusionShiny && variantColors && variantColors[this.fusionVariant]) { Object.keys(variantColors[this.fusionVariant]).forEach(k => { variantColorSet.set(Utils.rgbaToInt(Array.from(Object.values(Utils.rgbHexToRgba(k)))), Array.from(Object.values(Utils.rgbHexToRgba(variantColors[this.fusionVariant][k])))); }); } for (let i = 0; i < pixelData[2 + f].length; i += 4) { - const total = pixelData[2 + f].slice(i, i + 3).reduce((total: integer, value: integer) => total + value, 0); + const total = pixelData[2 + f].slice(i, i + 3).reduce((total: number, value: number) => total + value, 0); if (!total) { continue; } @@ -4010,18 +4018,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { fusionPaletteColors = fusionPaletteColors!; // TS compiler that fusionPaletteColors is defined! const [ palette, fusionPalette ] = [ paletteColors, fusionPaletteColors ] .map(paletteColors => { - let keys = Array.from(paletteColors.keys()).sort((a: integer, b: integer) => paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1); - let rgbaColors: Map; + let keys = Array.from(paletteColors.keys()).sort((a: number, b: number) => paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1); + let rgbaColors: Map; let hsvColors: Map; - const mappedColors = new Map(); + const mappedColors = new Map(); do { mappedColors.clear(); - rgbaColors = keys.reduce((map: Map, k: number) => { + rgbaColors = keys.reduce((map: Map, k: number) => { map.set(k, Object.values(rgbaFromArgb(k))); return map; - }, new Map()); + }, new Map()); hsvColors = Array.from(rgbaColors.keys()).reduce((map: Map, k: number) => { const rgb = rgbaColors.get(k)!.slice(0, 3); map.set(k, Utils.rgbToHsv(rgb[0], rgb[1], rgb[2])); @@ -4044,7 +4052,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - mappedColors.forEach((values: integer[], key: integer) => { + mappedColors.forEach((values: number[], key: number) => { const keyColor = rgbaColors.get(key)!; const valueColors = values.map(v => rgbaColors.get(v)!); const color = keyColor.slice(0); @@ -4059,7 +4067,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { for (let c = 0; c < 3; c++) { color[c] *= (paletteColors.get(key)! / count); - values.forEach((value: integer, i: integer) => { + values.forEach((value: number, i: number) => { if (paletteColors.has(value)) { const valueCount = paletteColors.get(value)!; color[c] += valueColors[i][c] * (valueCount / count); @@ -4079,7 +4087,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { paletteColors.set(argbFromRgba({ r: color[0], g: color[1], b: color[2], a: color[3] }), count); }); - keys = Array.from(paletteColors.keys()).sort((a: integer, b: integer) => paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1); + keys = Array.from(paletteColors.keys()).sort((a: number, b: number) => paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1); } while (mappedColors.size); return keys.map(c => Object.values(rgbaFromArgb(c))); @@ -4088,7 +4096,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const paletteDeltas: number[][] = []; - spriteColors.forEach((sc: integer[], i: integer) => { + spriteColors.forEach((sc: number[], i: number) => { paletteDeltas.push([]); for (let p = 0; p < palette.length; p++) { paletteDeltas[i].push(Utils.deltaRgb(sc, palette[p])); @@ -4131,7 +4139,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param min The minimum integer to pick, default `0` * @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) */ - randSeedInt(range: integer, min: integer = 0): integer { + randSeedInt(range: number, min: number = 0): number { return globalScene.currentBattle ? globalScene.randBattleSeedInt(range, min) : Utils.randSeedInt(range, min); @@ -4143,7 +4151,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param max The maximum integer to generate * @returns a random integer between {@linkcode min} and {@linkcode max} inclusive */ - randSeedIntRange(min: integer, max: integer): integer { + randSeedIntRange(min: number, max: number): number { return this.randSeedInt((max - min) + 1, min); } @@ -4154,9 +4162,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param hideInfo Indicates if this should also play the animation to hide the Pokemon's * info container. */ - leaveField(clearEffects: boolean = true, hideInfo: boolean = true) { + leaveField(clearEffects: boolean = true, hideInfo: boolean = true, destroy: boolean = false) { this.resetSprite(); this.resetTurnData(); + globalScene.getField(true).filter(p => p !== this).forEach(p => p.removeTagsBySourceId(this.id)); + if (clearEffects) { this.destroySubstitute(); this.resetSummonData(); // this also calls `resetBattleSummonData` @@ -4164,9 +4174,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (hideInfo) { this.hideInfo(); } - globalScene.field.remove(this); + // Trigger abilities that activate upon leaving the field + applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, this); this.setSwitchOutStatus(true); globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true); + globalScene.field.remove(this, destroy); } destroy(): void { @@ -4235,7 +4247,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { export class PlayerPokemon extends Pokemon { public compatibleTms: Moves[]; - constructor(species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { + constructor(species: PokemonSpecies, level: number, abilityIndex?: number, formIndex?: number, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: number[], nature?: Nature, dataSource?: Pokemon | PokemonData) { super(106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); if (Overrides.STATUS_OVERRIDE) { @@ -4280,7 +4292,7 @@ export class PlayerPokemon extends Pokemon { return false; } - getFieldIndex(): integer { + getFieldIndex(): number { return globalScene.getPlayerField().indexOf(this); } @@ -4337,7 +4349,7 @@ export class PlayerPokemon extends Pokemon { return new Promise(resolve => { this.leaveField(switchType === SwitchType.SWITCH); - globalScene.ui.setMode(Mode.PARTY, PartyUiMode.FAINT_SWITCH, this.getFieldIndex(), (slotIndex: integer, option: PartyOption) => { + globalScene.ui.setMode(Mode.PARTY, PartyUiMode.FAINT_SWITCH, this.getFieldIndex(), (slotIndex: number, option: PartyOption) => { if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { globalScene.prependToPhase(new SwitchSummonPhase(switchType, this.getFieldIndex(), slotIndex, false), MoveEndPhase); } @@ -4356,8 +4368,12 @@ export class PlayerPokemon extends Pokemon { ].filter(d => !!d); const amount = new Utils.NumberHolder(friendship); globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); - const candyFriendshipMultiplier = globalScene.eventManager.getClassicFriendshipMultiplier(); - const starterAmount = new Utils.NumberHolder(Math.floor(amount.value * (globalScene.gameMode.isClassic ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1))); + const candyFriendshipMultiplier = globalScene.gameMode.isClassic ? globalScene.eventManager.getClassicFriendshipMultiplier() : 1; + const fusionReduction = fusionStarterSpeciesId + ? globalScene.eventManager.areFusionsBoosted() ? 1.5 // Divide candy gain for fusions by 1.5 during events + : 2 // 2 for fusions outside events + : 1; // 1 for non-fused mons + const starterAmount = new Utils.NumberHolder(Math.floor(amount.value * candyFriendshipMultiplier / fusionReduction)); // Add friendship to this PlayerPokemon this.friendship = Math.min(this.friendship + amount.value, 255); @@ -4385,7 +4401,7 @@ export class PlayerPokemon extends Pokemon { */ revivalBlessing(): Promise { return new Promise(resolve => { - globalScene.ui.setMode(Mode.PARTY, PartyUiMode.REVIVAL_BLESSING, this.getFieldIndex(), (slotIndex:integer, option: PartyOption) => { + globalScene.ui.setMode(Mode.PARTY, PartyUiMode.REVIVAL_BLESSING, this.getFieldIndex(), (slotIndex:number, option: PartyOption) => { if (slotIndex >= 0 && slotIndex < 6) { const pokemon = globalScene.getPlayerParty()[slotIndex]; if (!pokemon || !pokemon.isFainted()) { @@ -4687,12 +4703,12 @@ export class PlayerPokemon extends Pokemon { export class EnemyPokemon extends Pokemon { public trainerSlot: TrainerSlot; public aiType: AiType; - public bossSegments: integer; - public bossSegmentIndex: integer; + public bossSegments: number; + public bossSegmentIndex: number; /** To indicate if the instance was populated with a dataSource -> e.g. loaded & populated from session data */ public readonly isPopulatedFromDataSource: boolean; - constructor(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, shinyLock: boolean = false, dataSource?: PokemonData) { + constructor(species: PokemonSpecies, level: number, trainerSlot: TrainerSlot, boss: boolean, shinyLock: boolean = false, dataSource?: PokemonData) { super(236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex, dataSource?.gender, (!shinyLock && dataSource) ? dataSource.shiny : false, (!shinyLock && dataSource) ? dataSource.variant : undefined, undefined, dataSource ? dataSource.nature : undefined, dataSource); @@ -4775,7 +4791,7 @@ export class EnemyPokemon extends Pokemon { * @param boss if the pokemon is a boss * @param bossSegments amount of boss segments (health-bar segments) */ - setBoss(boss: boolean = true, bossSegments: integer = 0): void { + setBoss(boss: boolean = true, bossSegments: number = 0): void { if (boss) { this.bossSegments = bossSegments || globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, this.level, this.species, true); this.bossSegmentIndex = this.bossSegments - 1; @@ -4785,7 +4801,7 @@ export class EnemyPokemon extends Pokemon { } } - generateAndPopulateMoveset(formIndex?: integer): void { + generateAndPopulateMoveset(formIndex?: number): void { switch (true) { case (this.species.speciesId === Species.SMEARGLE): this.moveset = [ @@ -4907,7 +4923,7 @@ export class EnemyPokemon extends Pokemon { const move = pokemonMove.getMove(); let moveScore = moveScores[m]; - const targetScores: integer[] = []; + const targetScores: number[] = []; for (const mt of moveTargets[move.id]) { // Prevent a target score from being calculated when the target is whoever attacks the user @@ -5047,9 +5063,9 @@ export class EnemyPokemon extends Pokemon { targetWeights = targetWeights.slice(0, benefitCutoffIndex); } - const thresholds: integer[] = []; - let totalWeight: integer = 0; - targetWeights.reduce((total: integer, w: integer) => { + const thresholds: number[] = []; + let totalWeight: number = 0; + targetWeights.reduce((total: number, w: number) => { total += w; thresholds.push(total); totalWeight = total; @@ -5062,7 +5078,7 @@ export class EnemyPokemon extends Pokemon { * is greater than that random number. */ const randValue = globalScene.randBattleSeedInt(totalWeight); - let targetIndex: integer = 0; + let targetIndex: number = 0; thresholds.every((t, i) => { if (randValue >= t) { @@ -5088,7 +5104,7 @@ export class EnemyPokemon extends Pokemon { return !!this.bossSegments; } - getBossSegmentIndex(): integer { + getBossSegmentIndex(): number { const segments = (this as EnemyPokemon).bossSegments; const segmentSize = this.getMaxHp() / segments; for (let s = segments - 1; s > 0; s--) { @@ -5101,7 +5117,7 @@ export class EnemyPokemon extends Pokemon { return 0; } - damage(damage: integer, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false): integer { + damage(damage: number, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false): number { if (this.isFainted()) { return 0; } @@ -5155,7 +5171,7 @@ export class EnemyPokemon extends Pokemon { return ret; } - canBypassBossSegments(segmentCount: integer = 1): boolean { + canBypassBossSegments(segmentCount: number = 1): boolean { if (globalScene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { if (!this.formIndex && (this.bossSegmentIndex - segmentCount) < 1) { return false; @@ -5172,7 +5188,7 @@ export class EnemyPokemon extends Pokemon { * For Pokemon with 5 health segments or more, breaking the last two shields give +2 each * @param segmentIndex index of the segment to get down to (0 = no shield left, 1 = 1 shield left, etc.) */ - handleBossSegmentCleared(segmentIndex: integer): void { + handleBossSegmentCleared(segmentIndex: number): void { while (this.bossSegmentIndex > 0 && segmentIndex - 1 < this.bossSegmentIndex) { // Filter out already maxed out stat stages and weigh the rest based on existing stats const leftoverStats = EFFECTIVE_STATS.filter((s: EffectiveStat) => this.getStatStage(s) < 6); @@ -5212,7 +5228,7 @@ export class EnemyPokemon extends Pokemon { } } - getFieldIndex(): integer { + getFieldIndex(): number { return globalScene.getEnemyField().indexOf(this); } @@ -5449,7 +5465,7 @@ export class PokemonMove { this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp()); } - getMovePp(): integer { + getMovePp(): number { return this.maxPpOverride || (this.getMove().pp + this.ppUp * Utils.toDmgValue(this.getMove().pp / 5)); } diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 2b74c1e5069..65bca641163 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -33,11 +33,11 @@ export enum TrainerVariant { export default class Trainer extends Phaser.GameObjects.Container { public config: TrainerConfig; public variant: TrainerVariant; - public partyTemplateIndex: integer; + public partyTemplateIndex: number; public name: string; public partnerName: string; - constructor(trainerType: TrainerType, variant: TrainerVariant, partyTemplateIndex?: integer, name?: string, partnerName?: string, trainerConfigOverride?: TrainerConfig) { + constructor(trainerType: TrainerType, variant: TrainerVariant, partyTemplateIndex?: number, name?: string, partnerName?: string, trainerConfigOverride?: TrainerConfig) { super(globalScene, -72, 80); this.config = trainerConfigs.hasOwnProperty(trainerType) ? trainerConfigs[trainerType] @@ -214,7 +214,7 @@ export default class Trainer extends Phaser.GameObjects.Container { return this.config.partyTemplates[this.partyTemplateIndex]; } - getPartyLevels(waveIndex: integer): integer[] { + getPartyLevels(waveIndex: number): number[] { const ret: number[] = []; const partyTemplate = this.getPartyTemplate(); @@ -262,7 +262,7 @@ export default class Trainer extends Phaser.GameObjects.Container { return ret; } - genPartyMember(index: integer): EnemyPokemon { + genPartyMember(index: number): EnemyPokemon { const battle = globalScene.currentBattle; const level = battle.enemyLevels?.[index]!; // TODO: is this bang correct? @@ -381,7 +381,7 @@ export default class Trainer extends Phaser.GameObjects.Container { } - genNewPartyMemberSpecies(level: integer, strength: PartyMemberStrength, attempt?: integer): PokemonSpecies { + genNewPartyMemberSpecies(level: number, strength: PartyMemberStrength, attempt?: number): PokemonSpecies { const battle = globalScene.currentBattle; const template = this.getPartyTemplate(); @@ -462,7 +462,7 @@ export default class Trainer extends Phaser.GameObjects.Container { return currentSpecies.includes(baseSpecies) || staticSpecies.includes(baseSpecies); } - getPartyMemberMatchupScores(trainerSlot: TrainerSlot = TrainerSlot.NONE, forSwitch: boolean = false): [integer, integer][] { + getPartyMemberMatchupScores(trainerSlot: TrainerSlot = TrainerSlot.NONE, forSwitch: boolean = false): [number, number][] { if (trainerSlot && !this.isDouble()) { trainerSlot = TrainerSlot.NONE; } @@ -487,12 +487,12 @@ export default class Trainer extends Phaser.GameObjects.Container { } return [ party.indexOf(p), score ]; - }) as [integer, integer][]; + }) as [number, number][]; return partyMemberScores; } - getSortedPartyMemberMatchupScores(partyMemberScores: [integer, integer][] = this.getPartyMemberMatchupScores()) { + getSortedPartyMemberMatchupScores(partyMemberScores: [number, number][] = this.getPartyMemberMatchupScores()) { const sortedPartyMemberScores = partyMemberScores.slice(0); sortedPartyMemberScores.sort((a, b) => { const scoreA = a[1]; @@ -503,7 +503,7 @@ export default class Trainer extends Phaser.GameObjects.Container { return sortedPartyMemberScores; } - getNextSummonIndex(trainerSlot: TrainerSlot = TrainerSlot.NONE, partyMemberScores: [integer, integer][] = this.getPartyMemberMatchupScores(trainerSlot)): integer { + getNextSummonIndex(trainerSlot: TrainerSlot = TrainerSlot.NONE, partyMemberScores: [number, number][] = this.getPartyMemberMatchupScores(trainerSlot)): number { if (trainerSlot && !this.isDouble()) { trainerSlot = TrainerSlot.NONE; } @@ -513,7 +513,7 @@ export default class Trainer extends Phaser.GameObjects.Container { const maxScorePartyMemberIndexes = partyMemberScores.filter(pms => pms[1] === sortedPartyMemberScores[0][1]).map(pms => pms[0]); if (maxScorePartyMemberIndexes.length > 1) { - let rand: integer; + let rand: number; globalScene.executeWithSeedOffset(() => rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length), globalScene.currentBattle.turn << 2); return maxScorePartyMemberIndexes[rand!]; } @@ -521,7 +521,7 @@ export default class Trainer extends Phaser.GameObjects.Container { return maxScorePartyMemberIndexes[0]; } - getPartyMemberModifierChanceMultiplier(index: integer): number { + getPartyMemberModifierChanceMultiplier(index: number): number { switch (this.getPartyTemplate().getStrength(index)) { case PartyMemberStrength.WEAKER: return 0.75; @@ -626,7 +626,7 @@ export default class Trainer extends Phaser.GameObjects.Container { return ret; } - tint(color: number, alpha?: number, duration?: integer, ease?: string): void { + tint(color: number, alpha?: number, duration?: number, ease?: string): void { const tintSprites = this.getTintSprites(); tintSprites.map(tintSprite => { tintSprite.setTintFill(color); @@ -647,7 +647,7 @@ export default class Trainer extends Phaser.GameObjects.Container { }); } - untint(duration: integer, ease?: string): void { + untint(duration: number, ease?: string): void { const tintSprites = this.getTintSprites(); tintSprites.map(tintSprite => { if (duration) { diff --git a/src/game-mode.ts b/src/game-mode.ts index 78a65a54890..1da125ea55a 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -91,7 +91,7 @@ export class GameMode implements GameModeConfig { * - 20 for Daily Runs * - 5 for all other modes */ - getStartingLevel(): integer { + getStartingLevel(): number { if (Overrides.STARTING_LEVEL_OVERRIDE) { return Overrides.STARTING_LEVEL_OVERRIDE; } @@ -108,7 +108,7 @@ export class GameMode implements GameModeConfig { * - override from overrides.ts * - 1000 */ - getStartingMoney(): integer { + getStartingMoney(): number { return Overrides.STARTING_MONEY_OVERRIDE || 1000; } @@ -127,7 +127,7 @@ export class GameMode implements GameModeConfig { } } - getWaveForDifficulty(waveIndex: integer, ignoreCurveChanges: boolean = false): integer { + getWaveForDifficulty(waveIndex: number, ignoreCurveChanges: boolean = false): number { switch (this.modeId) { case GameModes.DAILY: return waveIndex + 30 + (!ignoreCurveChanges ? Math.floor(waveIndex / 5) : 0); @@ -142,7 +142,7 @@ export class GameMode implements GameModeConfig { * @param arena the current {@linkcode Arena} * @returns `true` if a trainer should be generated, `false` otherwise */ - isWaveTrainer(waveIndex: integer, arena: Arena): boolean { + isWaveTrainer(waveIndex: number, arena: Arena): boolean { /** * Daily spawns trainers on floors 5, 15, 20, 25, 30, 35, 40, and 45 */ @@ -186,7 +186,7 @@ export class GameMode implements GameModeConfig { return false; } - isTrainerBoss(waveIndex: integer, biomeType: Biome, offsetGym: boolean): boolean { + isTrainerBoss(waveIndex: number, biomeType: Biome, offsetGym: boolean): boolean { switch (this.modeId) { case GameModes.DAILY: return waveIndex > 10 && waveIndex < 50 && !(waveIndex % 10); @@ -195,7 +195,7 @@ export class GameMode implements GameModeConfig { } } - getOverrideSpecies(waveIndex: integer): PokemonSpecies | null { + getOverrideSpecies(waveIndex: number): PokemonSpecies | null { if (this.isDaily && this.isWaveFinal(waveIndex)) { const allFinalBossSpecies = allSpecies.filter(s => (s.subLegendary || s.legendary || s.mythical) && s.baseTotal >= 600 && s.speciesId !== Species.ETERNATUS && s.speciesId !== Species.ARCEUS); @@ -211,7 +211,7 @@ export class GameMode implements GameModeConfig { * @param modeId game mode * @returns if the current wave is final for classic or daily OR a minor boss in endless */ - isWaveFinal(waveIndex: integer, modeId: GameModes = this.modeId): boolean { + isWaveFinal(waveIndex: number, modeId: GameModes = this.modeId): boolean { switch (modeId) { case GameModes.CLASSIC: case GameModes.CHALLENGE: @@ -228,7 +228,7 @@ export class GameMode implements GameModeConfig { * Every 10 waves is a boss battle * @returns true if waveIndex is a multiple of 10 */ - isBoss(waveIndex: integer): boolean { + isBoss(waveIndex: number): boolean { return waveIndex % 10 === 0; } @@ -244,7 +244,7 @@ export class GameMode implements GameModeConfig { * At this time it is paradox pokemon * @returns true if waveIndex is a multiple of 50 in Endless */ - isEndlessBoss(waveIndex: integer): boolean { + isEndlessBoss(waveIndex: number): boolean { return waveIndex % 50 === 0 && (this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS); } @@ -254,7 +254,7 @@ export class GameMode implements GameModeConfig { * At this time it is Eternatus * @returns true if waveIndex is a multiple of 250 in Endless */ - isEndlessMinorBoss(waveIndex: integer): boolean { + isEndlessMinorBoss(waveIndex: number): boolean { return waveIndex % 250 === 0 && (this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS); } @@ -264,27 +264,27 @@ export class GameMode implements GameModeConfig { * At this time it is Eternamax Eternatus * @returns true if waveIndex is a multiple of 1000 in Endless */ - isEndlessMajorBoss(waveIndex: integer): boolean { + isEndlessMajorBoss(waveIndex: number): boolean { return waveIndex % 1000 === 0 && (this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS); } /** * Checks whether there is a fixed battle on this gamemode on a given wave. - * @param {integer} waveIndex The wave to check. + * @param {number} waveIndex The wave to check. * @returns {boolean} If this game mode has a fixed battle on this wave */ - isFixedBattle(waveIndex: integer): boolean { + isFixedBattle(waveIndex: number): boolean { const dummyConfig = new FixedBattleConfig(); return this.battleConfig.hasOwnProperty(waveIndex) || applyChallenges(this, ChallengeType.FIXED_BATTLES, waveIndex, dummyConfig); } /** * Returns the config for the fixed battle for a particular wave. - * @param {integer} waveIndex The wave to check. + * @param {number} waveIndex The wave to check. * @returns {boolean} The fixed battle for this wave. */ - getFixedBattle(waveIndex: integer): FixedBattleConfig { + getFixedBattle(waveIndex: number): FixedBattleConfig { const challengeConfig = new FixedBattleConfig(); if (applyChallenges(this, ChallengeType.FIXED_BATTLES, waveIndex, challengeConfig)) { return challengeConfig; @@ -294,7 +294,7 @@ export class GameMode implements GameModeConfig { } - getClearScoreBonus(): integer { + getClearScoreBonus(): number { switch (this.modeId) { case GameModes.CLASSIC: case GameModes.CHALLENGE: @@ -306,7 +306,7 @@ export class GameMode implements GameModeConfig { } } - getEnemyModifierChance(isBoss: boolean): integer { + getEnemyModifierChance(isBoss: boolean): number { switch (this.modeId) { case GameModes.CLASSIC: case GameModes.CHALLENGE: diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 023f907a30d..e8f817c1c39 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"); @@ -246,9 +249,9 @@ export class LoadingScene extends SceneBase { } const availableLangs = [ "en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN" ]; if (lang && availableLangs.includes(lang)) { - this.loadImage("yearofthesnakeevent-" + lang, "events"); + this.loadImage("valentines2025event-" + lang, "events"); } else { - this.loadImage("yearofthesnakeevent-en", "events"); + this.loadImage("valentines2025event-en", "events"); } this.loadAtlas("statuses", ""); @@ -354,6 +357,7 @@ export class LoadingScene extends SceneBase { initVouchers(); initStatsKeys(); initPokemonPrevolutions(); + initPokemonStarters(); initBiomes(); initEggMoves(); initPokemonForms(); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index f531e96d641..b1e8b69df36 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -194,9 +194,9 @@ export interface GeneratedPersistentModifierType { class AddPokeballModifierType extends ModifierType { private pokeballType: PokeballType; - private count: integer; + private count: number; - constructor(iconImage: string, pokeballType: PokeballType, count: integer) { + constructor(iconImage: string, pokeballType: PokeballType, count: number) { super("", iconImage, (_type, _args) => new AddPokeballModifier(this, pokeballType, count), "pb", "se/pb_bounce_1"); this.pokeballType = pokeballType; this.count = count; @@ -221,9 +221,9 @@ class AddPokeballModifierType extends ModifierType { class AddVoucherModifierType extends ModifierType { private voucherType: VoucherType; - private count: integer; + private count: number; - constructor(voucherType: VoucherType, count: integer) { + constructor(voucherType: VoucherType, count: number) { super("", getVoucherTypeIcon(voucherType), (_type, _args) => new AddVoucherModifier(this, voucherType, count), "voucher"); this.count = count; this.voucherType = voucherType; @@ -276,11 +276,11 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { } export class PokemonHpRestoreModifierType extends PokemonModifierType { - protected restorePoints: integer; - protected restorePercent: integer; + protected restorePoints: number; + protected restorePercent: number; protected healStatus: boolean; - constructor(localeKey: string, iconImage: string, restorePoints: integer, restorePercent: integer, healStatus: boolean = false, newModifierFunc?: NewModifierFunc, selectFilter?: PokemonSelectFilter, group?: string) { + constructor(localeKey: string, iconImage: string, restorePoints: number, restorePercent: number, healStatus: boolean = false, newModifierFunc?: NewModifierFunc, selectFilter?: PokemonSelectFilter, group?: string) { super(localeKey, iconImage, newModifierFunc || ((_type, args) => new PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints, this.restorePercent, this.healStatus, false)), selectFilter || ((pokemon: PlayerPokemon) => { if (!pokemon.hp || (pokemon.isFullHp() && (!this.healStatus || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED))))) { @@ -307,7 +307,7 @@ export class PokemonHpRestoreModifierType extends PokemonModifierType { } export class PokemonReviveModifierType extends PokemonHpRestoreModifierType { - constructor(localeKey: string, iconImage: string, restorePercent: integer) { + constructor(localeKey: string, iconImage: string, restorePercent: number) { super(localeKey, iconImage, 0, restorePercent, false, (_type, args) => new PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, 0, this.restorePercent, false, true), ((pokemon: PlayerPokemon) => { if (!pokemon.isFainted()) { @@ -356,10 +356,10 @@ export abstract class PokemonMoveModifierType extends PokemonModifierType { } export class PokemonPpRestoreModifierType extends PokemonMoveModifierType { - protected restorePoints: integer; + protected restorePoints: number; - constructor(localeKey: string, iconImage: string, restorePoints: integer) { - super(localeKey, iconImage, (_type, args) => new PokemonPpRestoreModifier(this, (args[0] as PlayerPokemon).id, (args[1] as integer), this.restorePoints), + constructor(localeKey: string, iconImage: string, restorePoints: number) { + super(localeKey, iconImage, (_type, args) => new PokemonPpRestoreModifier(this, (args[0] as PlayerPokemon).id, (args[1] as number), this.restorePoints), (_pokemon: PlayerPokemon) => { return null; }, (pokemonMove: PokemonMove) => { @@ -381,9 +381,9 @@ export class PokemonPpRestoreModifierType extends PokemonMoveModifierType { } export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType { - protected restorePoints: integer; + protected restorePoints: number; - constructor(localeKey: string, iconImage: string, restorePoints: integer) { + constructor(localeKey: string, iconImage: string, restorePoints: number) { super(localeKey, iconImage, (_type, args) => new PokemonAllMovePpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints), (pokemon: PlayerPokemon) => { if (!pokemon.getMoveset().filter(m => m?.ppUsed).length) { @@ -404,10 +404,10 @@ export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType { } export class PokemonPpUpModifierType extends PokemonMoveModifierType { - protected upPoints: integer; + protected upPoints: number; - constructor(localeKey: string, iconImage: string, upPoints: integer) { - super(localeKey, iconImage, (_type, args) => new PokemonPpUpModifier(this, (args[0] as PlayerPokemon).id, (args[1] as integer), this.upPoints), + constructor(localeKey: string, iconImage: string, upPoints: number) { + super(localeKey, iconImage, (_type, args) => new PokemonPpUpModifier(this, (args[0] as PlayerPokemon).id, (args[1] as number), this.upPoints), (_pokemon: PlayerPokemon) => { return null; }, (pokemonMove: PokemonMove) => { @@ -451,7 +451,7 @@ export class PokemonNatureChangeModifierType extends PokemonModifierType { export class RememberMoveModifierType extends PokemonModifierType { constructor(localeKey: string, iconImage: string, group?: string) { - super(localeKey, iconImage, (type, args) => new RememberMoveModifier(type, (args[0] as PlayerPokemon).id, (args[1] as integer)), + super(localeKey, iconImage, (type, args) => new RememberMoveModifier(type, (args[0] as PlayerPokemon).id, (args[1] as number)), (pokemon: PlayerPokemon) => { if (!pokemon.getLearnableLevelMoves().length) { return PartyUiHandler.NoEffectMessage; @@ -552,9 +552,9 @@ enum AttackTypeBoosterItem { export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType { public moveType: Type; - public boostPercent: integer; + public boostPercent: number; - constructor(moveType: Type, boostPercent: integer) { + constructor(moveType: Type, boostPercent: number) { super("", `${AttackTypeBoosterItem[moveType]?.toLowerCase()}`, (_type, args) => new AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent)); @@ -657,9 +657,9 @@ export class BaseStatBoosterModifierType extends PokemonHeldItemModifierType imp * Shuckle Juice item */ export class PokemonBaseStatTotalModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType { - private readonly statModifier: integer; + private readonly statModifier: number; - constructor(statModifier: integer) { + constructor(statModifier: number) { super("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE", "berry_juice", (_type, args) => new PokemonBaseStatTotalModifier(this, (args[0] as Pokemon).id, this.statModifier)); this.statModifier = statModifier; } @@ -681,10 +681,10 @@ export class PokemonBaseStatTotalModifierType extends PokemonHeldItemModifierTyp * Old Gateau item */ export class PokemonBaseStatFlatModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType { - private readonly statModifier: integer; + private readonly statModifier: number; private readonly stats: Stat[]; - constructor(statModifier: integer, stats: Stat[]) { + constructor(statModifier: number, stats: Stat[]) { super("modifierType:ModifierType.MYSTERY_ENCOUNTER_OLD_GATEAU", "old_gateau", (_type, args) => new PokemonBaseStatFlatModifier(this, (args[0] as Pokemon).id, this.statModifier, this.stats)); this.statModifier = statModifier; this.stats = stats; @@ -746,9 +746,9 @@ export class MoneyRewardModifierType extends ModifierType { } export class ExpBoosterModifierType extends ModifierType { - private boostPercent: integer; + private boostPercent: number; - constructor(localeKey: string, iconImage: string, boostPercent: integer) { + constructor(localeKey: string, iconImage: string, boostPercent: number) { super(localeKey, iconImage, () => new ExpBoosterModifier(this, boostPercent)); this.boostPercent = boostPercent; @@ -760,9 +760,9 @@ export class ExpBoosterModifierType extends ModifierType { } export class PokemonExpBoosterModifierType extends PokemonHeldItemModifierType { - private boostPercent: integer; + private boostPercent: number; - constructor(localeKey: string, iconImage: string, boostPercent: integer) { + constructor(localeKey: string, iconImage: string, boostPercent: number) { super(localeKey, iconImage, (_type, args) => new PokemonExpBoosterModifier(this, (args[0] as Pokemon).id, boostPercent)); this.boostPercent = boostPercent; @@ -784,9 +784,9 @@ export class PokemonFriendshipBoosterModifierType extends PokemonHeldItemModifie } export class PokemonMoveAccuracyBoosterModifierType extends PokemonHeldItemModifierType { - private amount: integer; + private amount: number; - constructor(localeKey: string, iconImage: string, amount: integer, group?: string, soundName?: string) { + constructor(localeKey: string, iconImage: string, amount: number, group?: string, soundName?: string) { super(localeKey, iconImage, (_type, args) => new PokemonMoveAccuracyBoosterModifier(this, (args[0] as Pokemon).id, amount), group, soundName); this.amount = amount; @@ -934,7 +934,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { return null; } - const attackMoveTypeWeights = new Map(); + const attackMoveTypeWeights = new Map(); let totalWeight = 0; for (const t of attackMoveTypes) { if (attackMoveTypeWeights.has(t)) { @@ -1215,9 +1215,9 @@ export class TerastallizeModifierType extends PokemonHeldItemModifierType implem } export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemModifierType { - private chancePercent: integer; + private chancePercent: number; - constructor(localeKey: string, iconImage: string, chancePercent: integer, group?: string, soundName?: string) { + constructor(localeKey: string, iconImage: string, chancePercent: number, group?: string, soundName?: string) { super(localeKey, iconImage, (type, args) => new ContactHeldItemTransferChanceModifier(type, (args[0] as Pokemon).id, chancePercent), group, soundName); this.chancePercent = chancePercent; @@ -1239,10 +1239,10 @@ export class TurnHeldItemTransferModifierType extends PokemonHeldItemModifierTyp } export class EnemyAttackStatusEffectChanceModifierType extends ModifierType { - private chancePercent: integer; + private chancePercent: number; private effect: StatusEffect; - constructor(localeKey: string, iconImage: string, chancePercent: integer, effect: StatusEffect, stackCount?: integer) { + constructor(localeKey: string, iconImage: string, chancePercent: number, effect: StatusEffect, stackCount?: number) { super(localeKey, iconImage, (type, _args) => new EnemyAttackStatusEffectChanceModifier(type, effect, chancePercent, stackCount), "enemy_status_chance"); this.chancePercent = chancePercent; @@ -1272,7 +1272,7 @@ export class EnemyEndureChanceModifierType extends ModifierType { } export type ModifierTypeFunc = () => ModifierType; -type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: integer) => integer; +type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number; /** * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on @@ -1281,7 +1281,7 @@ type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: integer) * @param defaultWeight - ModifierType default weight * @returns A WeightedModifierTypeWeightFunc */ -function skipInClassicAfterWave(wave: integer, defaultWeight: integer): WeightedModifierTypeWeightFunc { +function skipInClassicAfterWave(wave: number, defaultWeight: number): WeightedModifierTypeWeightFunc { return () => { const gameMode = globalScene.gameMode; const currentWave = globalScene.currentBattle.waveIndex; @@ -1295,7 +1295,7 @@ function skipInClassicAfterWave(wave: integer, defaultWeight: integer): Weighted * @param defaultWeight ModifierType default weight * @returns A WeightedModifierTypeWeightFunc */ -function skipInLastClassicWaveOrDefault(defaultWeight: integer) : WeightedModifierTypeWeightFunc { +function skipInLastClassicWaveOrDefault(defaultWeight: number) : WeightedModifierTypeWeightFunc { return skipInClassicAfterWave(199, defaultWeight); } @@ -1314,10 +1314,10 @@ function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTyp } class WeightedModifierType { public modifierType: ModifierType; - public weight: integer | WeightedModifierTypeWeightFunc; - public maxWeight: integer | WeightedModifierTypeWeightFunc; + public weight: number | WeightedModifierTypeWeightFunc; + public maxWeight: number | WeightedModifierTypeWeightFunc; - constructor(modifierTypeFunc: ModifierTypeFunc, weight: integer | WeightedModifierTypeWeightFunc, maxWeight?: integer | WeightedModifierTypeWeightFunc) { + constructor(modifierTypeFunc: ModifierTypeFunc, weight: number | WeightedModifierTypeWeightFunc, maxWeight?: number | WeightedModifierTypeWeightFunc) { this.modifierType = modifierTypeFunc(); this.modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc)!; // TODO: is this bang correct? this.weight = weight; @@ -1715,13 +1715,22 @@ const modifierPool: ModifierPool = { if (!party.find(p => p.getLearnableLevelMoves().length)) { return 0; } - const highestPartyLevel = party.map(p => p.level).reduce((highestLevel: integer, level: integer) => Math.max(highestLevel, level), 1); + const highestPartyLevel = party.map(p => p.level).reduce((highestLevel: number, level: number) => Math.max(highestLevel, level), 1); return Math.min(Math.ceil(highestPartyLevel / 20), 4); }, 4), new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), new WeightedModifierType(modifierTypes.TERA_SHARD, 1), - new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => globalScene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 4 : 0), - new WeightedModifierType(modifierTypes.VOUCHER, (_party: Pokemon[], rerollCount: integer) => !globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0, 1), + new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => { + if (party.filter(p => !p.fusionSpecies).length > 1) { + if (globalScene.gameMode.isSplicedOnly) { + return 4; + } else if (globalScene.gameMode.isClassic && globalScene.eventManager.areFusionsBoosted()) { + return 1; + } + } + return 0; + }, 4), + new WeightedModifierType(modifierTypes.VOUCHER, (_party: Pokemon[], rerollCount: number) => !globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0, 1), ].map(m => { m.setTier(ModifierTier.GREAT); return m; }), @@ -1868,7 +1877,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.RARE_FORM_CHANGE_ITEM, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, 24), new WeightedModifierType(modifierTypes.MEGA_BRACELET, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, 36), new WeightedModifierType(modifierTypes.DYNAMAX_BAND, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, 36), - new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (_party: Pokemon[], rerollCount: integer) => !globalScene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, 3), + new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (_party: Pokemon[], rerollCount: number) => !globalScene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, 3), ].map(m => { m.setTier(ModifierTier.ROGUE); return m; }), @@ -1877,9 +1886,9 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.SHINY_CHARM, 14), new WeightedModifierType(modifierTypes.HEALING_CHARM, 18), new WeightedModifierType(modifierTypes.MULTI_LENS, 18), - new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (_party: Pokemon[], rerollCount: integer) => + new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (_party: Pokemon[], rerollCount: number) => !globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly ? Math.max(5 - rerollCount * 2, 0) : 0, 5), - new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => !globalScene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 : 0, 24), + new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => !(globalScene.gameMode.isClassic && globalScene.eventManager.areFusionsBoosted()) && !globalScene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 : 0, 24), new WeightedModifierType(modifierTypes.MINI_BLACK_HOLE, () => (globalScene.gameMode.isDaily || (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE))) ? 1 : 0, 1), ].map(m => { m.setTier(ModifierTier.MASTER); return m; @@ -2077,7 +2086,7 @@ const tierWeights = [ 768 / 1024, 195 / 1024, 48 / 1024, 12 / 1024, 1 / 1024 ]; */ export const itemPoolChecks: Map = new Map(); -export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount: integer = 0) { +export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount: number = 0) { const pool = getModifierPoolForType(poolType); itemPoolChecks.forEach((_v, k) => { itemPoolChecks.set(k, false); @@ -2091,7 +2100,7 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod const tierModifierIds: string[] = []; let tierMaxWeight = 0; let i = 0; - pool[t].reduce((total: integer, modifierType: WeightedModifierType) => { + pool[t].reduce((total: number, modifierType: WeightedModifierType) => { const weightedModifierType = modifierType as WeightedModifierType; const existingModifiers = globalScene.findModifiers(m => m.type.id === weightedModifierType.modifierType.id, poolType === ModifierPoolType.PLAYER); const itemModifierType = weightedModifierType.modifierType instanceof ModifierTypeGenerator @@ -2103,7 +2112,7 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod || existingModifiers.find(m => m.stackCount < m.getMaxStackCount(true)) ? weightedModifierType.weight instanceof Function ? (weightedModifierType.weight as Function)(party, rerollCount) - : weightedModifierType.weight as integer + : weightedModifierType.weight as number : 0; if (weightedModifierType.maxWeight) { const modifierId = weightedModifierType.modifierType.id; @@ -2187,7 +2196,7 @@ export function getModifierTypeFuncById(id: string): ModifierTypeFunc { * - `rerollMultiplier?: number` If specified, can adjust the amount of money required for a shop reroll. If set to a negative value, the shop will not allow rerolls at all. * - `allowLuckUpgrades?: boolean` Default `true`, if `false` will prevent set item tiers from upgrading via luck */ -export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemon[], modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings): ModifierTypeOption[] { +export function getPlayerModifierTypeOptions(count: number, party: PlayerPokemon[], modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings): ModifierTypeOption[] { const options: ModifierTypeOption[] = []; const retryCount = Math.min(count * 5, 50); if (!customModifierSettings) { @@ -2248,7 +2257,7 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo * @param tier If specified will generate item of tier * @param allowLuckUpgrades `true` to allow items to upgrade tiers (the little animation that plays and is affected by luck) */ -function getModifierTypeOptionWithRetry(existingOptions: ModifierTypeOption[], retryCount: integer, party: PlayerPokemon[], tier?: ModifierTier, allowLuckUpgrades?: boolean): ModifierTypeOption { +function getModifierTypeOptionWithRetry(existingOptions: ModifierTypeOption[], retryCount: number, party: PlayerPokemon[], tier?: ModifierTier, allowLuckUpgrades?: boolean): ModifierTypeOption { allowLuckUpgrades = allowLuckUpgrades ?? true; let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, tier, undefined, 0, allowLuckUpgrades); let r = 0; @@ -2283,7 +2292,7 @@ export function overridePlayerModifierTypeOptions(options: ModifierTypeOption[], } } -export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, baseCost: integer): ModifierTypeOption[] { +export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseCost: number): ModifierTypeOption[] { if (!(waveIndex % 10)) { return []; } @@ -2349,7 +2358,7 @@ export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: return modifier; } -export function getEnemyModifierTypesForWave(waveIndex: integer, count: integer, party: EnemyPokemon[], poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance: integer = 0): PokemonHeldItemModifierType[] { +export function getEnemyModifierTypesForWave(waveIndex: number, count: number, party: EnemyPokemon[], poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance: number = 0): PokemonHeldItemModifierType[] { const ret = new Array(count).fill(0).map(() => getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0)?.type as PokemonHeldItemModifierType); if (!(waveIndex % 1000)) { ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemModifierType); @@ -2393,7 +2402,7 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): PokemonHeld * @param retryCount Max allowed tries before the next tier down is checked for a valid ModifierType * @param allowLuckUpgrades Default true. If false, will not allow ModifierType to randomly upgrade to next tier */ -function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: integer, retryCount: integer = 0, allowLuckUpgrades: boolean = true): ModifierTypeOption | null { +function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: number, retryCount: number = 0, allowLuckUpgrades: boolean = true): ModifierTypeOption | null { const player = !poolType; const pool = getModifierPoolForType(poolType); let thresholds: object; @@ -2472,7 +2481,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, const tierThresholds = Object.keys(thresholds[tier]); const totalWeight = parseInt(tierThresholds[tierThresholds.length - 1]); const value = randSeedInt(totalWeight); - let index: integer | undefined; + let index: number | undefined; for (const t of tierThresholds) { const threshold = parseInt(t); if (value < threshold) { @@ -2514,10 +2523,10 @@ export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType export class ModifierTypeOption { public type: ModifierType; - public upgradeCount: integer; - public cost: integer; + public upgradeCount: number; + public cost: number; - constructor(type: ModifierType, upgradeCount: integer, cost: number = 0) { + constructor(type: ModifierType, upgradeCount: number, cost: number = 0) { this.type = type; this.upgradeCount = upgradeCount; this.cost = Math.min(Math.round(cost), Number.MAX_SAFE_INTEGER); @@ -2529,7 +2538,7 @@ export class ModifierTypeOption { * @param party The player's party. * @returns A number between 0 and 14 based on the party's total luck value, or a random number between 0 and 14 if the player is in Daily Run mode. */ -export function getPartyLuckValue(party: Pokemon[]): integer { +export function getPartyLuckValue(party: Pokemon[]): number { if (globalScene.gameMode.isDaily) { const DailyLuck = new NumberHolder(0); globalScene.executeWithSeedOffset(() => { @@ -2538,16 +2547,16 @@ export function getPartyLuckValue(party: Pokemon[]): integer { return DailyLuck.value; } const eventSpecies = globalScene.eventManager.getEventLuckBoostedSpecies(); - const luck = Phaser.Math.Clamp(party.map(p => p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 1 : 0) : 0) - .reduce((total: integer, value: integer) => total += value, 0), 0, 14); + const luck = Phaser.Math.Clamp(party.map(p => p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 3 : 0) : 0) + .reduce((total: number, value: number) => total += value, 0), 0, 14); return Math.min(globalScene.eventManager.getEventLuckBoost() + (luck ?? 0), 14); } -export function getLuckString(luckValue: integer): string { +export function getLuckString(luckValue: number): string { return [ "D", "C", "C+", "B-", "B", "B+", "A-", "A", "A+", "A++", "S", "S+", "SS", "SS+", "SSS" ][luckValue]; } -export function getLuckTextTint(luckValue: integer): integer { +export function getLuckTextTint(luckValue: number): number { let modifierTier: ModifierTier; if (luckValue > 11) { modifierTier = ModifierTier.LUXURY; diff --git a/src/overrides.ts b/src/overrides.ts index 06ad619a074..e53d3b766c4 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -126,9 +126,10 @@ class DefaultOverrides { /** * This will override the species of the fusion */ - readonly STARTER_FUSION_SPECIES_OVERRIDE: Species | integer = 0; + readonly STARTER_FUSION_SPECIES_OVERRIDE: Species | number = 0; readonly ABILITY_OVERRIDE: Abilities = Abilities.NONE; readonly PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE; + readonly HAS_PASSIVE_ABILITY_OVERRIDE: boolean | null = null; readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly GENDER_OVERRIDE: Gender | null = null; readonly MOVESET_OVERRIDE: Moves | Array = []; @@ -146,10 +147,11 @@ class DefaultOverrides { /** * This will override the species of the fusion only when the opponent is already a fusion */ - readonly OPP_FUSION_SPECIES_OVERRIDE: Species | integer = 0; + readonly OPP_FUSION_SPECIES_OVERRIDE: Species | number = 0; readonly OPP_LEVEL_OVERRIDE: number = 0; readonly OPP_ABILITY_OVERRIDE: Abilities = Abilities.NONE; readonly OPP_PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE; + readonly OPP_HAS_PASSIVE_ABILITY_OVERRIDE: boolean | null = null; readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly OPP_GENDER_OVERRIDE: Gender | null = null; readonly OPP_MOVESET_OVERRIDE: Moves | Array = []; diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 1f4fc0d6271..77a8043aee9 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -24,7 +24,7 @@ export class AttemptCapturePhase extends PokemonPhase { private pokeball: Phaser.GameObjects.Sprite; private originalY: number; - constructor(targetIndex: integer, pokeballType: PokeballType) { + constructor(targetIndex: number, pokeballType: PokeballType) { super(BattlerIndex.ENEMY + targetIndex); this.pokeballType = pokeballType; @@ -174,7 +174,7 @@ export class AttemptCapturePhase extends PokemonPhase { }); } - failCatch(shakeCount: integer) { + failCatch(shakeCount: number) { const pokemon = this.getPokemon(); globalScene.playSound("se/pb_rel"); @@ -241,11 +241,10 @@ export class AttemptCapturePhase extends PokemonPhase { }; const removePokemon = () => { globalScene.addFaintedEnemyScore(pokemon); - globalScene.getPlayerField().filter(p => p.isActive(true)).forEach(playerPokemon => playerPokemon.removeTagsBySourceId(pokemon.id)); pokemon.hp = 0; pokemon.trySetStatus(StatusEffect.FAINT); globalScene.clearEnemyHeldItemModifiers(); - globalScene.field.remove(pokemon, true); + pokemon.leaveField(true, true, true); }; const addToParty = (slotIndex?: number) => { const newPokemon = pokemon.addToParty(this.pokeballType, slotIndex); @@ -276,7 +275,7 @@ export class AttemptCapturePhase extends PokemonPhase { }); }, false); }, () => { - globalScene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { + globalScene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: number, _option: PartyOption) => { globalScene.ui.setMode(Mode.MESSAGE).then(() => { if (slotIndex < 6) { addToParty(slotIndex); diff --git a/src/phases/check-switch-phase.ts b/src/phases/check-switch-phase.ts index 06e08f7f30d..ea16e91b990 100644 --- a/src/phases/check-switch-phase.ts +++ b/src/phases/check-switch-phase.ts @@ -10,10 +10,10 @@ import { SwitchPhase } from "./switch-phase"; import { SwitchType } from "#enums/switch-type"; export class CheckSwitchPhase extends BattlePhase { - protected fieldIndex: integer; + protected fieldIndex: number; protected useName: boolean; - constructor(fieldIndex: integer, useName: boolean) { + constructor(fieldIndex: number, useName: boolean) { super(); this.fieldIndex = fieldIndex; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index e2bad953fc5..632344be335 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -25,9 +25,9 @@ import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagType } from "#app/enums/arena-tag-type"; export class CommandPhase extends FieldPhase { - protected fieldIndex: integer; + protected fieldIndex: number; - constructor(fieldIndex: integer) { + constructor(fieldIndex: number) { super(); this.fieldIndex = fieldIndex; @@ -113,7 +113,7 @@ export class CommandPhase extends FieldPhase { } } - handleCommand(command: Command, cursor: integer, ...args: any[]): boolean { + handleCommand(command: Command, cursor: number, ...args: any[]): boolean { const playerPokemon = globalScene.getPlayerField()[this.fieldIndex]; let success: boolean = false; @@ -319,7 +319,7 @@ export class CommandPhase extends FieldPhase { } } - getFieldIndex(): integer { + getFieldIndex(): number { return this.fieldIndex; } diff --git a/src/phases/common-anim-phase.ts b/src/phases/common-anim-phase.ts index 53cbdfaeb38..9ca74ed5a77 100644 --- a/src/phases/common-anim-phase.ts +++ b/src/phases/common-anim-phase.ts @@ -6,7 +6,7 @@ import { PokemonPhase } from "./pokemon-phase"; export class CommonAnimPhase extends PokemonPhase { private anim: CommonAnim | null; - private targetIndex: integer | undefined; + private targetIndex: number | undefined; private playOnEmptyField: boolean; constructor(battlerIndex?: BattlerIndex, targetIndex?: BattlerIndex, anim?: CommonAnim, playOnEmptyField: boolean = false) { diff --git a/src/phases/damage-anim-phase.ts b/src/phases/damage-anim-phase.ts index 2983d6b2de0..a21d9d4622a 100644 --- a/src/phases/damage-anim-phase.ts +++ b/src/phases/damage-anim-phase.ts @@ -6,11 +6,11 @@ import { fixedInt } from "#app/utils"; import { PokemonPhase } from "#app/phases/pokemon-phase"; export class DamageAnimPhase extends PokemonPhase { - private amount: integer; + private amount: number; private damageResult: DamageResult; private critical: boolean; - constructor(battlerIndex: BattlerIndex, amount: integer, damageResult?: DamageResult, critical: boolean = false) { + constructor(battlerIndex: BattlerIndex, amount: number, damageResult?: DamageResult, critical: boolean = false) { super(battlerIndex); this.amount = amount; @@ -35,7 +35,7 @@ export class DamageAnimPhase extends PokemonPhase { this.applyDamage(); } - updateAmount(amount: integer): void { + updateAmount(amount: number): void { this.amount = amount; } diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts index 11bf9584ee7..2d0aa24dd1f 100644 --- a/src/phases/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -28,7 +28,7 @@ export class EggHatchPhase extends Phase { private eggHatchData: EggHatchData; /** The number of eggs that are hatching */ - private eggsToHatchCount: integer; + private eggsToHatchCount: number; /** The container that lists how many eggs are hatching */ private eggCounterContainer: EggCounterContainer; @@ -59,7 +59,7 @@ export class EggHatchPhase extends Phase { /** The newly hatched {@link PlayerPokemon} */ private pokemon: PlayerPokemon; /** The index of which egg move is unlocked. 0-2 is common, 3 is rare */ - private eggMoveIndex: integer; + private eggMoveIndex: number; /** Internal booleans representing if the egg is hatched, able to be skipped, or skipped */ private hatched: boolean; private canSkip: boolean; @@ -68,7 +68,7 @@ export class EggHatchPhase extends Phase { private evolutionBgm: AnySound; private eggLapsePhase: EggLapsePhase; - constructor(hatchScene: EggLapsePhase, egg: Egg, eggsToHatchCount: integer) { + constructor(hatchScene: EggLapsePhase, egg: Egg, eggsToHatchCount: number) { super(); this.eggLapsePhase = hatchScene; this.egg = egg; @@ -221,7 +221,7 @@ export class EggHatchPhase extends Phase { * @param count the current number of times this function has been called. * @returns nothing since it's a Promise */ - doEggShake(intensity: number, repeatCount?: integer, count?: integer): Promise { + doEggShake(intensity: number, repeatCount?: number, count?: number): Promise { return new Promise(resolve => { if (repeatCount === undefined) { repeatCount = 0; @@ -382,7 +382,7 @@ export class EggHatchPhase extends Phase { * @param amplitude Scaling * @returns a number */ - sin(index: integer, amplitude: integer): number { + sin(index: number, amplitude: number): number { return amplitude * Math.sin(index * (Math.PI / 128)); } @@ -391,7 +391,7 @@ export class EggHatchPhase extends Phase { * @param intensity number of times this is repeated (this is a badly named variable) * @param offsetY how much to offset the Y coordinates */ - doSpray(intensity: integer, offsetY?: number) { + doSpray(intensity: number, offsetY?: number) { globalScene.tweens.addCounter({ repeat: intensity, duration: Utils.getFrameMs(1), @@ -406,7 +406,7 @@ export class EggHatchPhase extends Phase { * @param trigIndex Used to modify the particle's vertical speed, is a random number from 0-7 * @param offsetY how much to offset the Y coordinate */ - doSprayParticle(trigIndex: integer, offsetY: number) { + doSprayParticle(trigIndex: number, offsetY: number) { const initialX = this.eggHatchBg.displayWidth / 2; const initialY = this.eggHatchBg.displayHeight / 2 + offsetY; const shardKey = !this.egg.isManaphyEgg() ? this.egg.tier.toString() : "1"; diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 353dd6681cb..7e62ca17f7c 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -272,7 +272,7 @@ export class EncounterPhase extends BattlePhase { const enemyField = globalScene.getEnemyField(); globalScene.tweens.add({ targets: [ globalScene.arenaEnemy, globalScene.currentBattle.trainer, enemyField, globalScene.arenaPlayer, globalScene.trainer ].flat(), - x: (_target, _key, value, fieldIndex: integer) => fieldIndex < 2 + (enemyField.length) ? value + 300 : value - 300, + x: (_target, _key, value, fieldIndex: number) => fieldIndex < 2 + (enemyField.length) ? value + 300 : value - 300, duration: 2000, onComplete: () => { if (!this.tryOverrideForBattleSpec()) { diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts index 715303863be..e76518bb71e 100644 --- a/src/phases/enemy-command-phase.ts +++ b/src/phases/enemy-command-phase.ts @@ -15,10 +15,10 @@ import { BattlerTagType } from "#enums/battler-tag-type"; * @see {@linkcode EnemyPokemon.getNextMove} */ export class EnemyCommandPhase extends FieldPhase { - protected fieldIndex: integer; + protected fieldIndex: number; protected skipTurn: boolean = false; - constructor(fieldIndex: integer) { + constructor(fieldIndex: number) { super(); this.fieldIndex = fieldIndex; diff --git a/src/phases/enemy-party-member-pokemon-phase.ts b/src/phases/enemy-party-member-pokemon-phase.ts index 7c02cf97880..5c701f7d73a 100644 --- a/src/phases/enemy-party-member-pokemon-phase.ts +++ b/src/phases/enemy-party-member-pokemon-phase.ts @@ -2,7 +2,7 @@ import type { EnemyPokemon } from "#app/field/pokemon"; import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; export abstract class EnemyPartyMemberPokemonPhase extends PartyMemberPokemonPhase { - constructor(partyMemberIndex: integer) { + constructor(partyMemberIndex: number) { super(partyMemberIndex, false); } diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index bf046e682e4..533edd320e3 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -20,7 +20,7 @@ import { EVOLVE_MOVE } from "#app/data/balance/pokemon-level-moves"; export class EvolutionPhase extends Phase { protected pokemon: PlayerPokemon; - protected lastLevel: integer; + protected lastLevel: number; private preEvolvedPokemonName: string; @@ -39,7 +39,7 @@ export class EvolutionPhase extends Phase { protected pokemonEvoSprite: Phaser.GameObjects.Sprite; protected pokemonEvoTintSprite: Phaser.GameObjects.Sprite; - constructor(pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: integer) { + constructor(pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: number) { super(); this.pokemon = pokemon; @@ -356,7 +356,7 @@ export class EvolutionPhase extends Phase { }); } - doCycle(l: number, lastCycle: integer = 15): Promise { + doCycle(l: number, lastCycle: number = 15): Promise { return new Promise(resolve => { const isLastCycle = l === lastCycle; globalScene.tweens.add({ @@ -427,7 +427,7 @@ export class EvolutionPhase extends Phase { }); } - doSpiralUpwardParticle(trigIndex: integer) { + doSpiralUpwardParticle(trigIndex: number) { const initialX = this.evolutionBaseBg.displayWidth / 2; const particle = globalScene.add.image(initialX, 0, "evo_sparkle"); this.evolutionContainer.add(particle); @@ -463,7 +463,7 @@ export class EvolutionPhase extends Phase { updateParticle(); } - doArcDownParticle(trigIndex: integer) { + doArcDownParticle(trigIndex: number) { const initialX = this.evolutionBaseBg.displayWidth / 2; const particle = globalScene.add.image(initialX, 0, "evo_sparkle"); particle.setScale(0.5); @@ -496,7 +496,7 @@ export class EvolutionPhase extends Phase { updateParticle(); } - doCircleInwardParticle(trigIndex: integer, speed: integer) { + doCircleInwardParticle(trigIndex: number, speed: number) { const initialX = this.evolutionBaseBg.displayWidth / 2; const initialY = this.evolutionBaseBg.displayHeight / 2; const particle = globalScene.add.image(initialX, initialY, "evo_sparkle"); @@ -528,7 +528,7 @@ export class EvolutionPhase extends Phase { updateParticle(); } - doSprayParticle(trigIndex: integer) { + doSprayParticle(trigIndex: number) { const initialX = this.evolutionBaseBg.displayWidth / 2; const initialY = this.evolutionBaseBg.displayHeight / 2; const particle = globalScene.add.image(initialX, initialY, "evo_sparkle"); diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts index f0fe9fde719..7cf953851a1 100644 --- a/src/phases/exp-phase.ts +++ b/src/phases/exp-phase.ts @@ -9,7 +9,7 @@ import { LevelUpPhase } from "./level-up-phase"; export class ExpPhase extends PlayerPartyMemberPokemonPhase { private expValue: number; - constructor(partyMemberIndex: integer, expValue: number) { + constructor(partyMemberIndex: number, expValue: number) { super(partyMemberIndex); this.expValue = expValue; diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 7bf3bc81930..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]; @@ -181,9 +182,7 @@ export class FaintPhase extends PokemonPhase { y: pokemon.y + 150, ease: "Sine.easeIn", onComplete: () => { - pokemon.resetSprite(); pokemon.lapseTags(BattlerTagLapseType.FAINT); - globalScene.getField(true).filter(p => p !== pokemon).forEach(p => p.removeTagsBySourceId(pokemon.id)); pokemon.y -= 150; pokemon.trySetStatus(StatusEffect.FAINT); @@ -193,7 +192,7 @@ export class FaintPhase extends PokemonPhase { globalScene.addFaintedEnemyScore(pokemon as EnemyPokemon); globalScene.currentBattle.addPostBattleLoot(pokemon as EnemyPokemon); } - globalScene.field.remove(pokemon); + pokemon.leaveField(); this.end(); } }); diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index a11b797c574..c1f3042dbbe 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -29,7 +29,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { private learnMoveType: LearnMoveType; private cost: number; - constructor(partyMemberIndex: integer, moveId: Moves, learnMoveType: LearnMoveType = LearnMoveType.LEARN_MOVE, cost: number = -1) { + constructor(partyMemberIndex: number, moveId: Moves, learnMoveType: LearnMoveType = LearnMoveType.LEARN_MOVE, cost: number = -1) { super(partyMemberIndex); this.moveId = moveId; this.learnMoveType = learnMoveType; @@ -99,7 +99,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { async forgetMoveProcess(move: Move, pokemon: Pokemon) { globalScene.ui.setMode(this.messageMode); await globalScene.ui.showTextPromise(i18next.t("battle:learnMoveForgetQuestion"), undefined, true); - await globalScene.ui.setModeWithoutClear(Mode.SUMMARY, pokemon, SummaryUiMode.LEARN_MOVE, move, (moveIndex: integer) => { + await globalScene.ui.setModeWithoutClear(Mode.SUMMARY, pokemon, SummaryUiMode.LEARN_MOVE, move, (moveIndex: number) => { if (moveIndex === 4) { globalScene.ui.setMode(this.messageMode).then(() => this.rejectMoveAndEnd(move, pokemon)); return; diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts index 9439d8286c3..366fd324376 100644 --- a/src/phases/message-phase.ts +++ b/src/phases/message-phase.ts @@ -3,12 +3,12 @@ import { Phase } from "#app/phase"; export class MessagePhase extends Phase { private text: string; - private callbackDelay: integer | null; + private callbackDelay: number | null; private prompt: boolean | null; - private promptDelay: integer | null; + private promptDelay: number | null; private speaker?: string; - constructor(text: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null, speaker?: string) { + constructor(text: string, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null, speaker?: string) { super(); this.text = text; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index fff8caf38b5..be9a36940ea 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -95,6 +95,13 @@ export class MoveEffectPhase extends PokemonPhase { return super.end(); } + /** If an enemy used this move, set this as last enemy that used move or ability */ + if (!user.isPlayer()) { + globalScene.currentBattle.lastEnemyInvolved = this.fieldIndex; + } else { + globalScene.currentBattle.lastPlayerInvolved = this.fieldIndex; + } + const isDelayedAttack = this.move.getMove().hasAttr(DelayedAttackAttr); /** If the user was somehow removed from the field and it's not a delayed attack, end this phase */ if (!user.isOnField()) { diff --git a/src/phases/party-member-pokemon-phase.ts b/src/phases/party-member-pokemon-phase.ts index bc702c832b1..592f35834aa 100644 --- a/src/phases/party-member-pokemon-phase.ts +++ b/src/phases/party-member-pokemon-phase.ts @@ -3,11 +3,11 @@ import type Pokemon from "#app/field/pokemon"; import { FieldPhase } from "./field-phase"; export abstract class PartyMemberPokemonPhase extends FieldPhase { - protected partyMemberIndex: integer; - protected fieldIndex: integer; + protected partyMemberIndex: number; + protected fieldIndex: number; protected player: boolean; - constructor(partyMemberIndex: integer, player: boolean) { + constructor(partyMemberIndex: number, player: boolean) { super(); this.partyMemberIndex = partyMemberIndex; diff --git a/src/phases/player-party-member-pokemon-phase.ts b/src/phases/player-party-member-pokemon-phase.ts index bdedc0743e9..d97376a8614 100644 --- a/src/phases/player-party-member-pokemon-phase.ts +++ b/src/phases/player-party-member-pokemon-phase.ts @@ -2,7 +2,7 @@ import type { PlayerPokemon } from "#app/field/pokemon"; import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPhase { - constructor(partyMemberIndex: integer) { + constructor(partyMemberIndex: number) { super(partyMemberIndex, true); } diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index 268794ce97c..a544d47df70 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -15,7 +15,7 @@ import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { HealBlockTag } from "#app/data/battler-tags"; export class PokemonHealPhase extends CommonAnimPhase { - private hpHealed: integer; + private hpHealed: number; private message: string | null; private showFullHpMessage: boolean; private skipAnim: boolean; @@ -24,7 +24,7 @@ export class PokemonHealPhase extends CommonAnimPhase { private preventFullHeal: boolean; private fullRestorePP: boolean; - constructor(battlerIndex: BattlerIndex, hpHealed: integer, message: string | null, showFullHpMessage: boolean, skipAnim: boolean = false, revive: boolean = false, healStatus: boolean = false, preventFullHeal: boolean = false, fullRestorePP: boolean = false) { + constructor(battlerIndex: BattlerIndex, hpHealed: number, message: string | null, showFullHpMessage: boolean, skipAnim: boolean = false, revive: boolean = false, healStatus: boolean = false, preventFullHeal: boolean = false, fullRestorePP: boolean = false) { super(battlerIndex, undefined, CommonAnim.HEALTH_UP); this.hpHealed = hpHealed; diff --git a/src/phases/pokemon-phase.ts b/src/phases/pokemon-phase.ts index 29153ee726a..bf4193adb6d 100644 --- a/src/phases/pokemon-phase.ts +++ b/src/phases/pokemon-phase.ts @@ -4,11 +4,11 @@ import type Pokemon from "#app/field/pokemon"; import { FieldPhase } from "./field-phase"; export abstract class PokemonPhase extends FieldPhase { - protected battlerIndex: BattlerIndex | integer; + protected battlerIndex: BattlerIndex | number; public player: boolean; - public fieldIndex: integer; + public fieldIndex: number; - constructor(battlerIndex?: BattlerIndex | integer) { + constructor(battlerIndex?: BattlerIndex | number) { super(); if (battlerIndex === undefined) { diff --git a/src/phases/return-phase.ts b/src/phases/return-phase.ts index 9dd13f6d5be..8a876268c8e 100644 --- a/src/phases/return-phase.ts +++ b/src/phases/return-phase.ts @@ -4,7 +4,7 @@ import { SwitchType } from "#enums/switch-type"; import { SwitchSummonPhase } from "./switch-summon-phase"; export class ReturnPhase extends SwitchSummonPhase { - constructor(fieldIndex: integer) { + constructor(fieldIndex: number) { super(SwitchType.SWITCH, fieldIndex, -1, true); } diff --git a/src/phases/scan-ivs-phase.ts b/src/phases/scan-ivs-phase.ts index b3385a5de94..519d7913b79 100644 --- a/src/phases/scan-ivs-phase.ts +++ b/src/phases/scan-ivs-phase.ts @@ -9,9 +9,9 @@ import i18next from "i18next"; import { PokemonPhase } from "./pokemon-phase"; export class ScanIvsPhase extends PokemonPhase { - private shownIvs: integer; + private shownIvs: number; - constructor(battlerIndex: BattlerIndex, shownIvs: integer) { + constructor(battlerIndex: BattlerIndex, shownIvs: number) { super(battlerIndex); this.shownIvs = shownIvs; diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index 41077dfffc1..ea4dcc4274b 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -37,7 +37,7 @@ export class SelectBiomePhase extends BattlePhase { } else if (Array.isArray(biomeLinks[currentBiome])) { let biomes: Biome[] = []; globalScene.executeWithSeedOffset(() => { - biomes = (biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) + biomes = (biomeLinks[currentBiome] as (Biome | [Biome, number])[]) .filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1])) .map(b => !Array.isArray(b) ? b : b[0]); }, globalScene.currentBattle.waveIndex); @@ -46,7 +46,7 @@ export class SelectBiomePhase extends BattlePhase { globalScene.executeWithSeedOffset(() => { biomeChoices = (!Array.isArray(biomeLinks[currentBiome]) ? [ biomeLinks[currentBiome] as Biome ] - : biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) + : biomeLinks[currentBiome] as (Biome | [Biome, number])[]) .filter((b, i) => !Array.isArray(b) || !Utils.randSeedInt(b[1])) .map(b => Array.isArray(b) ? b[0] : b); }, globalScene.currentBattle.waveIndex); diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 6c8788c6a51..3b9a0a0405c 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -16,14 +16,14 @@ import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { isNullOrUndefined, NumberHolder } from "#app/utils"; export class SelectModifierPhase extends BattlePhase { - private rerollCount: integer; + private rerollCount: number; private modifierTiers?: ModifierTier[]; private customModifierSettings?: CustomModifierSettings; private isCopy: boolean; private typeOptions: ModifierTypeOption[]; - constructor(rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings, isCopy: boolean = false) { + constructor(rerollCount: number = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings, isCopy: boolean = false) { super(); this.rerollCount = rerollCount; @@ -66,7 +66,7 @@ export class SelectModifierPhase extends BattlePhase { this.typeOptions = this.getModifierTypeOptions(modifierCount.value); - const modifierSelectCallback = (rowCursor: integer, cursor: integer) => { + const modifierSelectCallback = (rowCursor: number, cursor: number) => { if (rowCursor < 0 || cursor < 0) { globalScene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { globalScene.ui.setOverlayMode(Mode.CONFIRM, () => { @@ -78,7 +78,7 @@ export class SelectModifierPhase extends BattlePhase { return false; } let modifierType: ModifierType; - let cost: integer; + let cost: number; const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); switch (rowCursor) { case 0: @@ -101,7 +101,7 @@ export class SelectModifierPhase extends BattlePhase { } break; case 1: - globalScene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { + globalScene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => { if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { const itemModifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; @@ -200,7 +200,7 @@ export class SelectModifierPhase extends BattlePhase { if (modifierType! instanceof PokemonModifierType) { //TODO: is the bang correct? if (modifierType instanceof FusePokemonModifierType) { - globalScene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.SPLICE, -1, (fromSlotIndex: integer, spliceSlotIndex: integer) => { + globalScene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.SPLICE, -1, (fromSlotIndex: number, spliceSlotIndex: number) => { if (spliceSlotIndex !== undefined && fromSlotIndex < 6 && spliceSlotIndex < 6 && fromSlotIndex !== spliceSlotIndex) { globalScene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct? @@ -223,13 +223,13 @@ export class SelectModifierPhase extends BattlePhase { const tmMoveId = isTmModifier ? (modifierType as TmModifierType).moveId : undefined; - globalScene.ui.setModeWithoutClear(Mode.PARTY, partyUiMode, -1, (slotIndex: integer, option: PartyOption) => { + globalScene.ui.setModeWithoutClear(Mode.PARTY, partyUiMode, -1, (slotIndex: number, option: PartyOption) => { if (slotIndex < 6) { globalScene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { const modifier = !isMoveModifier ? !isRememberMoveModifier ? modifierType.newModifier(party[slotIndex]) - : modifierType.newModifier(party[slotIndex], option as integer) + : modifierType.newModifier(party[slotIndex], option as number) : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); applyModifier(modifier!, true); // TODO: is the bang correct? }); @@ -291,7 +291,7 @@ export class SelectModifierPhase extends BattlePhase { return ModifierPoolType.PLAYER; } - getModifierTypeOptions(modifierCount: integer): ModifierTypeOption[] { + getModifierTypeOptions(modifierCount: number): ModifierTypeOption[] { return getPlayerModifierTypeOptions(modifierCount, globalScene.getPlayerParty(), globalScene.lockModifierTiers ? this.modifierTiers : undefined, this.customModifierSettings); } diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 20ba414147a..79012901a3a 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -26,7 +26,7 @@ export class SelectStarterPhase extends Phase { globalScene.ui.setMode(Mode.STARTER_SELECT, (starters: Starter[]) => { globalScene.ui.clearText(); - globalScene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { + globalScene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => { if (slotId === -1) { globalScene.clearPhaseQueue(); globalScene.pushPhase(new TitlePhase()); @@ -45,7 +45,7 @@ export class SelectStarterPhase extends Phase { initBattle(starters: Starter[]) { const party = globalScene.getPlayerParty(); const loadPokemonAssets: Promise[] = []; - starters.forEach((starter: Starter, i: integer) => { + starters.forEach((starter: Starter, i: number) => { if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); } diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts index b21415746fb..a30ef9000a5 100644 --- a/src/phases/select-target-phase.ts +++ b/src/phases/select-target-phase.ts @@ -8,7 +8,7 @@ import i18next from "#app/plugins/i18n"; import { allMoves } from "#app/data/move"; export class SelectTargetPhase extends PokemonPhase { - constructor(fieldIndex: integer) { + constructor(fieldIndex: number) { super(fieldIndex); } diff --git a/src/phases/show-ability-phase.ts b/src/phases/show-ability-phase.ts index a0db660ded5..d759ad833a1 100644 --- a/src/phases/show-ability-phase.ts +++ b/src/phases/show-ability-phase.ts @@ -17,6 +17,14 @@ export class ShowAbilityPhase extends PokemonPhase { const pokemon = this.getPokemon(); if (pokemon) { + + if (!pokemon.isPlayer()) { + /** If its an enemy pokemon, list it as last enemy to use ability or move */ + globalScene.currentBattle.lastEnemyInvolved = pokemon.getBattlerIndex() % 2; + } else { + globalScene.currentBattle.lastPlayerInvolved = pokemon.getBattlerIndex() % 2; + } + globalScene.abilityBar.showAbility(pokemon, this.passive); if (pokemon?.battleData) { diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts index 71bd39e3898..004592bc335 100644 --- a/src/phases/show-party-exp-bar-phase.ts +++ b/src/phases/show-party-exp-bar-phase.ts @@ -10,7 +10,7 @@ import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-pha export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { private expValue: number; - constructor(partyMemberIndex: integer, expValue: number) { + constructor(partyMemberIndex: number, expValue: number) { super(partyMemberIndex); this.expValue = expValue; diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index f2e95446139..753d1f7cede 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; import type { BattlerIndex } from "#app/battle"; -import { applyAbAttrs, applyPostStatStageChangeAbAttrs, applyPreStatStageChangeAbAttrs, PostStatStageChangeAbAttr, ProtectStatAbAttr, StatStageChangeCopyAbAttr, StatStageChangeMultiplierAbAttr } from "#app/data/ability"; +import { applyAbAttrs, applyPostStatStageChangeAbAttrs, applyPreStatStageChangeAbAttrs, PostStatStageChangeAbAttr, ProtectStatAbAttr, ReflectStatStageChangeAbAttr, StatStageChangeCopyAbAttr, StatStageChangeMultiplierAbAttr } from "#app/data/ability"; import { ArenaTagSide, MistTag } from "#app/data/arena-tag"; +import type { ArenaTag } from "#app/data/arena-tag"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { ResetNegativeStatStageModifier } from "#app/modifier/modifier"; @@ -10,20 +11,24 @@ import { NumberHolder, BooleanHolder } from "#app/utils"; import i18next from "i18next"; import { PokemonPhase } from "./pokemon-phase"; import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat"; +import { OctolockTag } from "#app/data/battler-tags"; +import { ArenaTagType } from "#app/enums/arena-tag-type"; export type StatStageChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void; export class StatStageChangePhase extends PokemonPhase { private stats: BattleStat[]; private selfTarget: boolean; - private stages: integer; + private stages: number; private showMessage: boolean; private ignoreAbilities: boolean; private canBeCopied: boolean; private onChange: StatStageChangeCallback | null; + private comingFromMirrorArmorUser: boolean; + private comingFromStickyWeb: boolean; - constructor(battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], stages: integer, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatStageChangeCallback | null = null) { + constructor(battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], stages: number, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatStageChangeCallback | null = null, comingFromMirrorArmorUser: boolean = false, comingFromStickyWeb: boolean = false) { super(battlerIndex); this.selfTarget = selfTarget; @@ -33,6 +38,8 @@ export class StatStageChangePhase extends PokemonPhase { this.ignoreAbilities = ignoreAbilities; this.canBeCopied = canBeCopied; this.onChange = onChange; + this.comingFromMirrorArmorUser = comingFromMirrorArmorUser; + this.comingFromStickyWeb = comingFromStickyWeb; } start() { @@ -41,12 +48,44 @@ export class StatStageChangePhase extends PokemonPhase { if (this.stats.length > 1) { for (let i = 0; i < this.stats.length; i++) { const stat = [ this.stats[i] ]; - globalScene.unshiftPhase(new StatStageChangePhase(this.battlerIndex, this.selfTarget, stat, this.stages, this.showMessage, this.ignoreAbilities, this.canBeCopied, this.onChange)); + globalScene.unshiftPhase(new StatStageChangePhase(this.battlerIndex, this.selfTarget, stat, this.stages, this.showMessage, this.ignoreAbilities, this.canBeCopied, this.onChange, this.comingFromMirrorArmorUser)); } return this.end(); } const pokemon = this.getPokemon(); + let opponentPokemon: Pokemon | undefined; + + /** Gets the position of last enemy or player pokemon that used ability or move, primarily for double battles involving Mirror Armor */ + if (pokemon.isPlayer()) { + /** If this SSCP is not from sticky web, then we find the opponent pokemon that last did something */ + if (!this.comingFromStickyWeb) { + opponentPokemon = globalScene.getEnemyField()[globalScene.currentBattle.lastEnemyInvolved]; + } else { + /** If this SSCP is from sticky web, then check if pokemon that last sucessfully used sticky web is on field */ + const stickyTagID = globalScene.arena.findTagsOnSide( + (t: ArenaTag) => t.tagType === ArenaTagType.STICKY_WEB, + ArenaTagSide.PLAYER)[0].sourceId; + globalScene.getEnemyField().forEach((e) => { + if (e.id === stickyTagID) { + opponentPokemon = e; + } + }); + } + } else { + if (!this.comingFromStickyWeb) { + opponentPokemon = globalScene.getPlayerField()[globalScene.currentBattle.lastPlayerInvolved]; + } else { + const stickyTagID = globalScene.arena.findTagsOnSide( + (t: ArenaTag) => t.tagType === ArenaTagType.STICKY_WEB, + ArenaTagSide.ENEMY)[0].sourceId; + globalScene.getPlayerField().forEach((e) => { + if (e.id === stickyTagID) { + opponentPokemon = e; + } + }); + } + } if (!pokemon.isActive(true)) { return this.end(); @@ -70,6 +109,11 @@ export class StatStageChangePhase extends PokemonPhase { if (!cancelled.value && !this.selfTarget && stages.value < 0) { applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, pokemon, stat, cancelled, simulate); + + /** Potential stat reflection due to Mirror Armor, does not apply to Octolock end of turn effect */ + if (opponentPokemon !== undefined && !pokemon.findTag(t => t instanceof OctolockTag) && !this.comingFromMirrorArmorUser) { + applyPreStatStageChangeAbAttrs(ReflectStatStageChangeAbAttr, pokemon, stat, cancelled, simulate, opponentPokemon, this.stages); + } } // If one stat stage decrease is cancelled, simulate the rest of the applications @@ -211,7 +255,7 @@ export class StatStageChangePhase extends PokemonPhase { } } - getStatStageChangeMessages(stats: BattleStat[], stages: integer, relStages: integer[]): string[] { + getStatStageChangeMessages(stats: BattleStat[], stages: number, relStages: number[]): string[] { const messages: string[] = []; const relStageStatIndexes = {}; diff --git a/src/phases/summon-missing-phase.ts b/src/phases/summon-missing-phase.ts index f32f6a377f5..459a0399964 100644 --- a/src/phases/summon-missing-phase.ts +++ b/src/phases/summon-missing-phase.ts @@ -4,7 +4,7 @@ import { SummonPhase } from "./summon-phase"; import { globalScene } from "#app/global-scene"; export class SummonMissingPhase extends SummonPhase { - constructor(fieldIndex: integer) { + constructor(fieldIndex: number) { super(fieldIndex); } diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index 72f5ec8640f..09eded9e52f 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -18,7 +18,7 @@ import { globalScene } from "#app/global-scene"; export class SummonPhase extends PartyMemberPokemonPhase { private loaded: boolean; - constructor(fieldIndex: integer, player: boolean = true, loaded: boolean = false) { + constructor(fieldIndex: number, player: boolean = true, loaded: boolean = false) { super(fieldIndex, player); this.loaded = loaded; diff --git a/src/phases/switch-phase.ts b/src/phases/switch-phase.ts index e763aee1521..34b70aeaf63 100644 --- a/src/phases/switch-phase.ts +++ b/src/phases/switch-phase.ts @@ -11,7 +11,7 @@ import { SwitchSummonPhase } from "./switch-summon-phase"; * for the player (if a switch would be valid for the current battle state). */ export class SwitchPhase extends BattlePhase { - protected readonly fieldIndex: integer; + protected readonly fieldIndex: number; private readonly switchType: SwitchType; private readonly isModal: boolean; private readonly doReturn: boolean; @@ -25,7 +25,7 @@ export class SwitchPhase extends BattlePhase { * @param doReturn Indicates if the party member on the field should be * recalled to ball or has already left the field. Passed to {@linkcode SwitchSummonPhase}. */ - constructor(switchType: SwitchType, fieldIndex: integer, isModal: boolean, doReturn: boolean) { + constructor(switchType: SwitchType, fieldIndex: number, isModal: boolean, doReturn: boolean) { super(); this.switchType = switchType; @@ -61,7 +61,7 @@ export class SwitchPhase extends BattlePhase { // Override field index to 0 in case of double battle where 2/3 remaining legal party members fainted at once const fieldIndex = globalScene.currentBattle.getBattlerCount() === 1 || globalScene.getPokemonAllowedInBattle().length > 1 ? this.fieldIndex : 0; - globalScene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { + globalScene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: number, option: PartyOption) => { if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { // Remove any pre-existing PostSummonPhase under the same field index. // Pre-existing PostSummonPhases may occur when this phase is invoked during a prompt to switch at the start of a wave. diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 94e57435922..dad0f6f11ad 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -16,7 +16,7 @@ import { SwitchType } from "#enums/switch-type"; export class SwitchSummonPhase extends SummonPhase { private readonly switchType: SwitchType; - private readonly slotIndex: integer; + private readonly slotIndex: number; private readonly doReturn: boolean; private lastPokemon: Pokemon; @@ -29,7 +29,7 @@ export class SwitchSummonPhase extends SummonPhase { * @param doReturn boolean whether to render "comeback" dialogue * @param player boolean if the switch is from the player */ - constructor(switchType: SwitchType, fieldIndex: integer, slotIndex: integer, doReturn: boolean, player?: boolean) { + constructor(switchType: SwitchType, fieldIndex: number, slotIndex: number, doReturn: boolean, player?: boolean) { super(fieldIndex, player !== undefined ? player : true); this.switchType = switchType; @@ -64,6 +64,7 @@ export class SwitchSummonPhase extends SummonPhase { const pokemon = this.getPokemon(); (this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); + if (this.switchType === SwitchType.SWITCH || this.switchType === SwitchType.INITIAL_SWITCH) { const substitute = pokemon.getTag(SubstituteTag); if (substitute) { @@ -93,8 +94,8 @@ export class SwitchSummonPhase extends SummonPhase { ease: "Sine.easeIn", scale: 0.5, onComplete: () => { - pokemon.leaveField(this.switchType === SwitchType.SWITCH, false); globalScene.time.delayedCall(750, () => this.switchAndSummon()); + pokemon.leaveField(this.switchType === SwitchType.SWITCH, false); } }); } diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 86b5fe04a09..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; } }, @@ -133,7 +134,7 @@ export class TitlePhase extends Phase { label: i18next.t("menu:loadGame"), handler: () => { globalScene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, - (slotId: integer) => { + (slotId: number) => { if (slotId === -1) { return this.showOptions(); } @@ -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: () => { @@ -174,7 +167,7 @@ export class TitlePhase extends Phase { globalScene.ui.setMode(Mode.TITLE, config); } - loadSaveSlot(slotId: integer): void { + loadSaveSlot(slotId: number): void { globalScene.sessionSlotId = slotId > -1 || !loggedInUser ? slotId : loggedInUser.lastSessionSlot; globalScene.ui.setMode(Mode.MESSAGE); globalScene.ui.resetModeChain(); @@ -192,7 +185,8 @@ export class TitlePhase extends Phase { } initDailyRun(): void { - globalScene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { + globalScene.ui.clearText(); + globalScene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => { globalScene.clearPhaseQueue(); if (slotId === -1) { globalScene.pushPhase(new TitlePhase()); diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index 8918b664a36..13e04569ef3 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -18,7 +18,7 @@ export class VictoryPhase extends PokemonPhase { /** If true, indicates that the phase is intended for EXP purposes only, and not to continue a battle to next phase */ isExpOnly: boolean; - constructor(battlerIndex: BattlerIndex | integer, isExpOnly: boolean = false) { + constructor(battlerIndex: BattlerIndex | number, isExpOnly: boolean = false) { super(battlerIndex); this.isExpOnly = isExpOnly; diff --git a/src/pipelines/sprite.ts b/src/pipelines/sprite.ts index 1fc3b771897..67639d6450a 100644 --- a/src/pipelines/sprite.ts +++ b/src/pipelines/sprite.ts @@ -351,7 +351,7 @@ export default class SpritePipeline extends FieldSpritePipeline { const data = sprite.pipelineData; const tone = data["tone"] as number[]; - const teraColor = data["teraColor"] as integer[] ?? [ 0, 0, 0 ]; + const teraColor = data["teraColor"] as number[] ?? [ 0, 0, 0 ]; const hasShadow = data["hasShadow"] as boolean; const yShadowOffset = data["yShadowOffset"] as number; const ignoreFieldPos = data["ignoreFieldPos"] as boolean; @@ -389,8 +389,8 @@ export default class SpritePipeline extends FieldSpritePipeline { const fusionSpriteColors = ((ignoreOverride && data["fusionSpriteColorsBase"]) || data["fusionSpriteColors"] || []) as number[][]; const emptyColors = [ 0, 0, 0, 0 ]; - const flatSpriteColors: integer[] = []; - const flatFusionSpriteColors: integer[] = []; + const flatSpriteColors: number[] = []; + const flatFusionSpriteColors: number[] = []; for (let c = 0; c < 32; c++) { flatSpriteColors.splice(flatSpriteColors.length, 0, ...(c < spriteColors.length ? spriteColors[c] : emptyColors)); flatFusionSpriteColors.splice(flatFusionSpriteColors.length, 0, ...(c < fusionSpriteColors.length ? fusionSpriteColors[c] : emptyColors)); @@ -406,14 +406,14 @@ export default class SpritePipeline extends FieldSpritePipeline { const sprite = (gameObject as Phaser.GameObjects.Sprite); const data = sprite.pipelineData; - const variant: integer = data.hasOwnProperty("variant") + const variant: number = data.hasOwnProperty("variant") ? data["variant"] : sprite.parentContainer instanceof Pokemon ? sprite.parentContainer.variant : 0; let variantColors; const emptyColors = [ 0, 0, 0, 0 ]; - const flatBaseColors: integer[] = []; + const flatBaseColors: number[] = []; const flatVariantColors: number[] = []; if ((sprite.parentContainer instanceof Pokemon ? sprite.parentContainer.shiny : !!data["shiny"]) 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/scene-base.ts b/src/scene-base.ts index 9af97b8e6d4..c6ca9bb8ba2 100644 --- a/src/scene-base.ts +++ b/src/scene-base.ts @@ -41,7 +41,7 @@ export class SceneBase extends Phaser.Scene { } } - loadSpritesheet(key: string, folder: string, size: integer, filename?: string) { + loadSpritesheet(key: string, folder: string, size: number, filename?: string) { if (!filename) { filename = `${key}.png`; } diff --git a/src/system/achv.ts b/src/system/achv.ts index e0c9f0cf052..bd9348a52bf 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -25,7 +25,7 @@ export class Achv { public name: string; public description: string; public iconImage: string; - public score: integer; + public score: number; public secret: boolean; public hasParent: boolean; @@ -33,7 +33,7 @@ export class Achv { private conditionFunc: ConditionFn | undefined; - constructor(localizationKey:string, name: string, description: string, iconImage: string, score: integer, conditionFunc?: ConditionFn) { + constructor(localizationKey:string, name: string, description: string, iconImage: string, score: number, conditionFunc?: ConditionFn) { this.name = name; this.description = description; this.iconImage = iconImage; @@ -89,58 +89,58 @@ export class Achv { } export class MoneyAchv extends Achv { - moneyAmount: integer; + moneyAmount: number; - constructor(localizationKey: string, name: string, moneyAmount: integer, iconImage: string, score: integer) { + constructor(localizationKey: string, name: string, moneyAmount: number, iconImage: string, score: number) { super(localizationKey, name, "", iconImage, score, (_args: any[]) => globalScene.money >= this.moneyAmount); this.moneyAmount = moneyAmount; } } export class RibbonAchv extends Achv { - ribbonAmount: integer; + ribbonAmount: number; - constructor(localizationKey: string, name: string, ribbonAmount: integer, iconImage: string, score: integer) { + constructor(localizationKey: string, name: string, ribbonAmount: number, iconImage: string, score: number) { super(localizationKey, name, "", iconImage, score, (_args: any[]) => globalScene.gameData.gameStats.ribbonsOwned >= this.ribbonAmount); this.ribbonAmount = ribbonAmount; } } export class DamageAchv extends Achv { - damageAmount: integer; + damageAmount: number; - constructor(localizationKey: string, name: string, damageAmount: integer, iconImage: string, score: integer) { + constructor(localizationKey: string, name: string, damageAmount: number, iconImage: string, score: number) { super(localizationKey, name, "", iconImage, score, (args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.damageAmount); this.damageAmount = damageAmount; } } export class HealAchv extends Achv { - healAmount: integer; + healAmount: number; - constructor(localizationKey: string, name: string, healAmount: integer, iconImage: string, score: integer) { + constructor(localizationKey: string, name: string, healAmount: number, iconImage: string, score: number) { super(localizationKey, name, "", iconImage, score, (args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.healAmount); this.healAmount = healAmount; } } export class LevelAchv extends Achv { - level: integer; + level: number; - constructor(localizationKey: string, name: string, level: integer, iconImage: string, score: integer) { + constructor(localizationKey: string, name: string, level: number, iconImage: string, score: number) { super(localizationKey, name, "", iconImage, score, (args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.level); this.level = level; } } export class ModifierAchv extends Achv { - constructor(localizationKey: string, name: string, description: string, iconImage: string, score: integer, modifierFunc: (modifier: Modifier) => boolean) { + constructor(localizationKey: string, name: string, description: string, iconImage: string, score: number, modifierFunc: (modifier: Modifier) => boolean) { super(localizationKey, name, description, iconImage, score, (args: any[]) => modifierFunc((args[0] as Modifier))); } } export class ChallengeAchv extends Achv { - constructor(localizationKey: string, name: string, description: string, iconImage: string, score: integer, challengeFunc: (challenge: Challenge) => boolean) { + constructor(localizationKey: string, name: string, description: string, iconImage: string, score: number, challengeFunc: (challenge: Challenge) => boolean) { super(localizationKey, name, description, iconImage, score, (args: any[]) => challengeFunc(args[0] as Challenge)); } } @@ -370,7 +370,7 @@ export const achvs = { export function initAchievements() { const achvKeys = Object.keys(achvs); - achvKeys.forEach((a: string, i: integer) => { + achvKeys.forEach((a: string, i: number) => { achvs[a].id = a; if (achvs[a].hasParent) { achvs[a].parentId = achvKeys[i - 1]; diff --git a/src/system/challenge-data.ts b/src/system/challenge-data.ts index 76a3392861d..87299ae653a 100644 --- a/src/system/challenge-data.ts +++ b/src/system/challenge-data.ts @@ -2,9 +2,9 @@ import type { Challenge } from "#app/data/challenge"; import { copyChallenge } from "#app/data/challenge"; export default class ChallengeData { - public id: integer; - public value: integer; - public severity: integer; + public id: number; + public value: number; + public severity: number; constructor(source: Challenge | any) { this.id = source.id; diff --git a/src/system/egg-data.ts b/src/system/egg-data.ts index 7b75f387c63..8296c2da98e 100644 --- a/src/system/egg-data.ts +++ b/src/system/egg-data.ts @@ -5,11 +5,11 @@ import { EGG_SEED, Egg } from "../data/egg"; import type { EggSourceType } from "#app/enums/egg-source-types"; export default class EggData { - public id: integer; + public id: number; public tier: EggTier; public sourceType: EggSourceType; - public hatchWaves: integer; - public timestamp: integer; + public hatchWaves: number; + public timestamp: number; public variantTier: VariantTier; public isShiny: boolean; public species: Species; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 58d416eb468..7282d2730a4 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -70,7 +70,7 @@ export const defaultStarterSpecies: Species[] = [ const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary -export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): string { +export function getDataTypeKey(dataType: GameDataType, slotId: number = 0): string { switch (dataType) { case GameDataType.SYSTEM: return "data"; @@ -104,8 +104,8 @@ export function decrypt(data: string, bypassLogin: boolean): string { } export interface SystemSaveData { - trainerId: integer; - secretId: integer; + trainerId: number; + secretId: number; gender: PlayerGender; dexData: DexData; starterData: StarterData; @@ -116,14 +116,14 @@ export interface SystemSaveData { voucherCounts: VoucherCounts; eggs: EggData[]; gameVersion: string; - timestamp: integer; - eggPity: integer[]; - unlockPity: integer[]; + timestamp: number; + eggPity: number[]; + unlockPity: number[]; } export interface SessionSaveData { seed: string; - playTime: integer; + playTime: number; gameMode: GameModes; party: PokemonData[]; enemyParty: PokemonData[]; @@ -131,46 +131,46 @@ export interface SessionSaveData { enemyModifiers: PersistentModifierData[]; arena: ArenaData; pokeballCounts: PokeballCounts; - money: integer; - score: integer; - waveIndex: integer; + money: number; + score: number; + waveIndex: number; battleType: BattleType; trainer: TrainerData; gameVersion: string; - timestamp: integer; + timestamp: number; challenges: ChallengeData[]; mysteryEncounterType: MysteryEncounterType | -1; // Only defined when current wave is ME, mysteryEncounterSaveData: MysteryEncounterSaveData; } interface Unlocks { - [key: integer]: boolean; + [key: number]: boolean; } interface AchvUnlocks { - [key: string]: integer + [key: string]: number } interface VoucherUnlocks { - [key: string]: integer + [key: string]: number } export interface VoucherCounts { - [type: string]: integer; + [type: string]: number; } export interface DexData { - [key: integer]: DexEntry + [key: number]: DexEntry } export interface DexEntry { seenAttr: bigint; caughtAttr: bigint; - natureAttr: integer, - seenCount: integer; - caughtCount: integer; - hatchedCount: integer; - ivs: integer[]; + natureAttr: number, + seenCount: number; + caughtCount: number; + hatchedCount: number; + ivs: number[]; } export const DexAttr = { @@ -188,7 +188,7 @@ export interface DexAttrProps { shiny: boolean; female: boolean; variant: Variant; - formIndex: integer; + formIndex: number; } export const AbilityAttr = { @@ -209,18 +209,18 @@ export interface RunEntry { export type StarterMoveset = [ Moves ] | [ Moves, Moves ] | [ Moves, Moves, Moves ] | [ Moves, Moves, Moves, Moves ]; export interface StarterFormMoveData { - [key: integer]: StarterMoveset + [key: number]: StarterMoveset } export interface StarterMoveData { - [key: integer]: StarterMoveset | StarterFormMoveData + [key: number]: StarterMoveset | StarterFormMoveData } export interface StarterAttributes { - nature?: integer; - ability?: integer; - variant?: integer; - form?: integer; + nature?: number; + ability?: number; + variant?: number; + form?: number; female?: boolean; shiny?: boolean; favorite?: boolean; @@ -228,7 +228,7 @@ export interface StarterAttributes { } export interface StarterPreferences { - [key: integer]: StarterAttributes; + [key: number]: StarterAttributes; } // the latest data saved/loaded for the Starter Preferences. Required to reduce read/writes. Initialize as "{}", since this is the default value and no data needs to be stored if present. @@ -263,17 +263,17 @@ export class StarterPrefs { export interface StarterDataEntry { moveset: StarterMoveset | StarterFormMoveData | null; - eggMoves: integer; - candyCount: integer; - friendship: integer; - abilityAttr: integer; - passiveAttr: integer; - valueReduction: integer; - classicWinCount: integer; + eggMoves: number; + candyCount: number; + friendship: number; + abilityAttr: number; + passiveAttr: number; + valueReduction: number; + classicWinCount: number; } export interface StarterData { - [key: integer]: StarterDataEntry + [key: number]: StarterDataEntry } export interface TutorialFlags { @@ -303,8 +303,8 @@ const systemShortKeys = { }; export class GameData { - public trainerId: integer; - public secretId: integer; + public trainerId: number; + public secretId: number; public gender: PlayerGender; @@ -323,8 +323,8 @@ export class GameData { public voucherUnlocks: VoucherUnlocks; public voucherCounts: VoucherCounts; public eggs: Egg[]; - public eggPity: integer[]; - public unlockPity: integer[]; + public eggPity: number[]; + public unlockPity: number[]; constructor() { this.loadSettings(); @@ -733,7 +733,7 @@ export class GameData { * @param valueIndex index of the setting's option * @returns true */ - public saveSetting(setting: string, valueIndex: integer): boolean { + public saveSetting(setting: string, valueIndex: number): boolean { let settings: object = {}; if (localStorage.hasOwnProperty("settings")) { settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct? @@ -816,7 +816,7 @@ export class GameData { * to update the specified setting with the new value. Finally, it saves the updated settings back * to localStorage and returns `true` to indicate success. */ - public saveControlSetting(device: Device, localStoragePropertyName: string, setting: SettingGamepad|SettingKeyboard, settingDefaults, valueIndex: integer): boolean { + public saveControlSetting(device: Device, localStoragePropertyName: string, setting: SettingGamepad|SettingKeyboard, settingDefaults, valueIndex: number): boolean { let settingsControls: object = {}; // Initialize an empty object to hold the gamepad settings if (localStorage.hasOwnProperty(localStoragePropertyName)) { // Check if 'settingsControls' exists in localStorage @@ -968,7 +968,7 @@ export class GameData { } as SessionSaveData; } - getSession(slotId: integer): Promise { + getSession(slotId: number): Promise { return new Promise(async (resolve, reject) => { if (slotId < 0) { return resolve(null); @@ -1006,7 +1006,7 @@ export class GameData { }); } - loadSession(slotId: integer, sessionData?: SessionSaveData): Promise { + loadSession(slotId: number, sessionData?: SessionSaveData): Promise { return new Promise(async (resolve, reject) => { try { const initSessionFromData = async (sessionData: SessionSaveData) => { @@ -1137,7 +1137,7 @@ export class GameData { * @param slotId the slot to clear * @returns Promise with result `true` if the session was deleted successfully, `false` otherwise */ - deleteSession(slotId: integer): Promise { + deleteSession(slotId: number): Promise { return new Promise(resolve => { if (bypassLogin) { localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); @@ -1206,7 +1206,7 @@ export class GameData { * After session data is removed, attempt to update user info so the menu updates * To delete an unfinished run instead, use {@linkcode deleteSession} */ - async tryClearSession(slotId: integer): Promise<[success: boolean, newClear: boolean]> { + async tryClearSession(slotId: number): Promise<[success: boolean, newClear: boolean]> { let result: [boolean, boolean] = [ false, false ]; if (bypassLogin) { @@ -1361,7 +1361,7 @@ export class GameData { }); } - public tryExportData(dataType: GameDataType, slotId: integer = 0): Promise { + public tryExportData(dataType: GameDataType, slotId: number = 0): Promise { return new Promise(resolve => { const dataKey: string = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`; const handleData = (dataStr: string) => { @@ -1407,7 +1407,7 @@ export class GameData { }); } - public importData(dataType: GameDataType, slotId: integer = 0): void { + public importData(dataType: GameDataType, slotId: number = 0): void { const dataKey = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`; let saveFile: any = document.getElementById("saveFile"); @@ -1695,7 +1695,7 @@ export class GameData { }); } - incrementRibbonCount(species: PokemonSpecies, forStarter: boolean = false): integer { + incrementRibbonCount(species: PokemonSpecies, forStarter: boolean = false): number { const speciesIdToIncrement: Species = species.getRootSpeciesId(forStarter); if (!this.starterData[speciesIdToIncrement].classicWinCount) { @@ -1706,7 +1706,7 @@ export class GameData { globalScene.gameData.gameStats.ribbonsOwned++; } - const ribbonsInStats: integer = globalScene.gameData.gameStats.ribbonsOwned; + const ribbonsInStats: number = globalScene.gameData.gameStats.ribbonsOwned; if (ribbonsInStats >= 100) { globalScene.validateAchv(achvs._100_RIBBONS); @@ -1733,7 +1733,7 @@ export class GameData { * @param species * @param count */ - addStarterCandy(species: PokemonSpecies, count: integer): void { + addStarterCandy(species: PokemonSpecies, count: number): void { // Only gain candies if the Pokemon has already been marked as caught in dex (ignore "rental" pokemon) const speciesRootForm = species.getRootSpeciesId(); if (globalScene.gameData.dexData[speciesRootForm].caughtAttr) { @@ -1749,7 +1749,7 @@ export class GameData { * @param showMessage Default true. If true, will display message for unlocked egg move * @param prependSpeciesToMessage Default false. If true, will change message from "X Egg Move Unlocked!" to "Bulbasaur X Egg Move Unlocked!" */ - setEggMoveUnlocked(species: PokemonSpecies, eggMoveIndex: integer, showMessage: boolean = true, prependSpeciesToMessage: boolean = false): Promise { + setEggMoveUnlocked(species: PokemonSpecies, eggMoveIndex: number, showMessage: boolean = true, prependSpeciesToMessage: boolean = false): Promise { return new Promise(resolve => { const speciesId = species.speciesId; if (!speciesEggMoves.hasOwnProperty(speciesId) || !speciesEggMoves[speciesId][eggMoveIndex]) { @@ -1808,7 +1808,7 @@ export class GameData { _unlockSpeciesNature(species.speciesId); } - updateSpeciesDexIvs(speciesId: Species, ivs: integer[]): void { + updateSpeciesDexIvs(speciesId: Species, ivs: number[]): void { let dexEntry: DexEntry; do { dexEntry = globalScene.gameData.dexData[speciesId]; @@ -1824,7 +1824,7 @@ export class GameData { } while (pokemonPrevolutions.hasOwnProperty(speciesId) && (speciesId = pokemonPrevolutions[speciesId])); } - getSpeciesCount(dexEntryPredicate: (entry: DexEntry) => boolean): integer { + getSpeciesCount(dexEntryPredicate: (entry: DexEntry) => boolean): number { const dexKeys = Object.keys(this.dexData); let speciesCount = 0; for (const s of dexKeys) { @@ -1835,7 +1835,7 @@ export class GameData { return speciesCount; } - getStarterCount(dexEntryPredicate: (entry: DexEntry) => boolean): integer { + getStarterCount(dexEntryPredicate: (entry: DexEntry) => boolean): number { const starterKeys = Object.keys(speciesStarterCosts); let starterCount = 0; for (const s of starterKeys) { @@ -1906,7 +1906,7 @@ export class GameData { }; } - getStarterSpeciesDefaultAbilityIndex(species: PokemonSpecies): integer { + getStarterSpeciesDefaultAbilityIndex(species: PokemonSpecies): number { const abilityAttr = this.starterData[species.speciesId].abilityAttr; return abilityAttr & AbilityAttr.ABILITY_1 ? 0 : !species.ability2 || abilityAttr & AbilityAttr.ABILITY_2 ? 1 : 2; } @@ -1921,15 +1921,15 @@ export class GameData { return 0 as Nature; } - getSpeciesDefaultNatureAttr(species: PokemonSpecies): integer { + getSpeciesDefaultNatureAttr(species: PokemonSpecies): number { return 1 << (this.getSpeciesDefaultNature(species)); } - getDexAttrLuck(dexAttr: bigint): integer { + getDexAttrLuck(dexAttr: bigint): number { return dexAttr & DexAttr.SHINY ? dexAttr & DexAttr.VARIANT_3 ? 3 : dexAttr & DexAttr.VARIANT_2 ? 2 : 1 : 0; } - getNaturesForAttr(natureAttr: integer = 0): Nature[] { + getNaturesForAttr(natureAttr: number = 0): Nature[] { const ret: Nature[] = []; for (let n = 0; n < 25; n++) { if (natureAttr & (1 << (n + 1))) { @@ -1962,7 +1962,7 @@ export class GameData { return cost.value; } - getFormIndex(attr: bigint): integer { + getFormIndex(attr: bigint): number { if (!attr || attr < DexAttr.DEFAULT_FORM) { return 0; } @@ -1973,7 +1973,7 @@ export class GameData { return f; } - getFormAttr(formIndex: integer): bigint { + getFormAttr(formIndex: number): bigint { return BigInt(1) << BigInt(7 + formIndex); } diff --git a/src/system/game-stats.ts b/src/system/game-stats.ts index a8b74f29bb7..5553a0332fc 100644 --- a/src/system/game-stats.ts +++ b/src/system/game-stats.ts @@ -1,43 +1,43 @@ -// public (.*?): integer; +// public (.*?): number; // this.$1 = source?.$1 || 0; export class GameStats { - public playTime: integer; - public battles: integer; - public classicSessionsPlayed: integer; - public sessionsWon: integer; - public ribbonsOwned: integer; - public dailyRunSessionsPlayed: integer; - public dailyRunSessionsWon: integer; - public endlessSessionsPlayed: integer; - public highestEndlessWave: integer; - public highestLevel: integer; - public highestMoney: integer; - public highestDamage: integer; - public highestHeal: integer; - public pokemonSeen: integer; - public pokemonDefeated: integer; - public pokemonCaught: integer; - public pokemonHatched: integer; - public subLegendaryPokemonSeen: integer; - public subLegendaryPokemonCaught: integer; - public subLegendaryPokemonHatched: integer; - public legendaryPokemonSeen: integer; - public legendaryPokemonCaught: integer; - public legendaryPokemonHatched: integer; - public mythicalPokemonSeen: integer; - public mythicalPokemonCaught: integer; - public mythicalPokemonHatched: integer; - public shinyPokemonSeen: integer; - public shinyPokemonCaught: integer; - public shinyPokemonHatched: integer; - public pokemonFused: integer; - public trainersDefeated: integer; - public eggsPulled: integer; - public rareEggsPulled: integer; - public epicEggsPulled: integer; - public legendaryEggsPulled: integer; - public manaphyEggsPulled: integer; + public playTime: number; + public battles: number; + public classicSessionsPlayed: number; + public sessionsWon: number; + public ribbonsOwned: number; + public dailyRunSessionsPlayed: number; + public dailyRunSessionsWon: number; + public endlessSessionsPlayed: number; + public highestEndlessWave: number; + public highestLevel: number; + public highestMoney: number; + public highestDamage: number; + public highestHeal: number; + public pokemonSeen: number; + public pokemonDefeated: number; + public pokemonCaught: number; + public pokemonHatched: number; + public subLegendaryPokemonSeen: number; + public subLegendaryPokemonCaught: number; + public subLegendaryPokemonHatched: number; + public legendaryPokemonSeen: number; + public legendaryPokemonCaught: number; + public legendaryPokemonHatched: number; + public mythicalPokemonSeen: number; + public mythicalPokemonCaught: number; + public mythicalPokemonHatched: number; + public shinyPokemonSeen: number; + public shinyPokemonCaught: number; + public shinyPokemonHatched: number; + public pokemonFused: number; + public trainersDefeated: number; + public eggsPulled: number; + public rareEggsPulled: number; + public epicEggsPulled: number; + public legendaryEggsPulled: number; + public manaphyEggsPulled: number; constructor(source?: any) { this.playTime = source?.playTime || 0; diff --git a/src/system/modifier-data.ts b/src/system/modifier-data.ts index d153236c93e..4816115f586 100644 --- a/src/system/modifier-data.ts +++ b/src/system/modifier-data.ts @@ -8,7 +8,7 @@ export default class ModifierData { public typeId: string; public typePregenArgs: any[]; public args: any[]; - public stackCount: integer; + public stackCount: number; public className: string; diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 92eca5c3e9f..6459bb5033d 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -15,47 +15,47 @@ import type { Species } from "#enums/species"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; export default class PokemonData { - public id: integer; + public id: number; public player: boolean; public species: Species; public nickname: string; - public formIndex: integer; - public abilityIndex: integer; + public formIndex: number; + public abilityIndex: number; public passive: boolean; public shiny: boolean; public variant: Variant; public pokeball: PokeballType; - public level: integer; - public exp: integer; - public levelExp: integer; + public level: number; + public exp: number; + public levelExp: number; public gender: Gender; - public hp: integer; - public stats: integer[]; - public ivs: integer[]; + public hp: number; + public stats: number[]; + public ivs: number[]; public nature: Nature; public moveset: (PokemonMove | null)[]; public status: Status | null; - public friendship: integer; - public metLevel: integer; + public friendship: number; + public metLevel: number; public metBiome: Biome | -1; // -1 for starters public metSpecies: Species; public metWave: number; // 0 for unknown (previous saves), -1 for starters - public luck: integer; + public luck: number; public pauseEvolutions: boolean; public pokerus: boolean; public usedTMs: Moves[]; - public evoCounter: integer; + public evoCounter: number; public fusionSpecies: Species; - public fusionFormIndex: integer; - public fusionAbilityIndex: integer; + public fusionFormIndex: number; + public fusionAbilityIndex: number; public fusionShiny: boolean; public fusionVariant: Variant; public fusionGender: Gender; - public fusionLuck: integer; + public fusionLuck: number; public boss: boolean; - public bossSegments?: integer; + public bossSegments?: number; public summonData: PokemonSummonData; @@ -163,7 +163,7 @@ export default class PokemonData { } } - toPokemon(battleType?: BattleType, partyMemberIndex: integer = 0, double: boolean = false): Pokemon { + toPokemon(battleType?: BattleType, partyMemberIndex: number = 0, double: boolean = false): Pokemon { const species = getPokemonSpecies(this.species); const ret: Pokemon = this.player ? globalScene.addPlayerPokemon(species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this, (playerPokemon) => { diff --git a/src/system/session-history.ts b/src/system/session-history.ts index e6901b2fa93..d9dd2022d8f 100644 --- a/src/system/session-history.ts +++ b/src/system/session-history.ts @@ -10,13 +10,13 @@ export enum SessionHistoryResult { export interface SessionHistory { seed: string; - playTime: integer; + playTime: number; result: SessionHistoryResult, gameMode: GameModes; party: PokemonData[]; modifiers: PersistentModifierData[]; - money: integer; - waveIndex: integer; + money: number; + waveIndex: number; gameVersion: string; - timestamp: integer; + timestamp: number; } diff --git a/src/system/settings/settings-gamepad.ts b/src/system/settings/settings-gamepad.ts index 18973666766..d26c0c9f019 100644 --- a/src/system/settings/settings-gamepad.ts +++ b/src/system/settings/settings-gamepad.ts @@ -80,7 +80,7 @@ export const settingGamepadBlackList = [ SettingKeyboard.Button_Right, ]; -export function setSettingGamepad(setting: SettingGamepad, value: integer): boolean { +export function setSettingGamepad(setting: SettingGamepad, value: number): boolean { switch (setting) { case SettingGamepad.Gamepad_Support: // if we change the value of the gamepad support, we call a method in the inputController to diff --git a/src/system/settings/settings-keyboard.ts b/src/system/settings/settings-keyboard.ts index f4adc30ee52..95ad4ba41f1 100644 --- a/src/system/settings/settings-keyboard.ts +++ b/src/system/settings/settings-keyboard.ts @@ -133,7 +133,7 @@ export const settingKeyboardBlackList = [ ]; -export function setSettingKeyboard(setting: SettingKeyboard, value: integer): boolean { +export function setSettingKeyboard(setting: SettingKeyboard, value: number): boolean { switch (setting) { case SettingKeyboard.Button_Up: case SettingKeyboard.Button_Down: diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 44fc1ee4f31..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 @@ -713,7 +725,7 @@ export function resetSettings() { * @param value value to update setting with * @returns true if successful, false if not */ -export function setSetting(setting: string, value: integer): boolean { +export function setSetting(setting: string, value: number): boolean { const index: number = settingIndex(setting); if (index === -1) { return false; @@ -828,6 +840,9 @@ export function setSetting(setting: string, value: integer): 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/system/trainer-data.ts b/src/system/trainer-data.ts index 51dfdde9ec1..134d16e25ef 100644 --- a/src/system/trainer-data.ts +++ b/src/system/trainer-data.ts @@ -4,7 +4,7 @@ import Trainer, { TrainerVariant } from "../field/trainer"; export default class TrainerData { public trainerType: TrainerType; public variant: TrainerVariant; - public partyTemplateIndex: integer; + public partyTemplateIndex: number; public name: string; public partnerName: string; diff --git a/src/test/abilities/desolate-land.test.ts b/src/test/abilities/desolate-land.test.ts new file mode 100644 index 00000000000..75576d7a8f6 --- /dev/null +++ b/src/test/abilities/desolate-land.test.ts @@ -0,0 +1,139 @@ +import { PokeballType } from "#app/enums/pokeball"; +import { WeatherType } from "#app/enums/weather-type"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; + +describe("Abilities - Desolate Land", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset(Moves.SPLASH) + .hasPassiveAbility(true) + .enemySpecies(Species.RALTS) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + /** + * This checks that the weather has changed after the Enemy Pokemon with {@linkcode Abilities.DESOLATE_LAND} + * is forcefully moved out of the field from moves such as Roar {@linkcode Moves.ROAR} + */ + it("should lift only when all pokemon with this ability leave the field", async () => { + game.override + .battleType("double") + .enemyMoveset([ Moves.SPLASH, Moves.ROAR ]); + await game.classicMode.startBattle([ Species.MAGCARGO, Species.MAGCARGO, Species.MAGIKARP, Species.MAGIKARP ]); + + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN); + + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min; + }); + + game.move.select(Moves.SPLASH, 0, 2); + game.move.select(Moves.SPLASH, 1, 2); + + await game.forceEnemyMove(Moves.ROAR, 0); + await game.forceEnemyMove(Moves.SPLASH, 1); + + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN); + + await game.toNextTurn(); + + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min + 1; + }); + + game.move.select(Moves.SPLASH, 0, 2); + game.move.select(Moves.SPLASH, 1, 2); + + await game.forceEnemyMove(Moves.ROAR, 1); + await game.forceEnemyMove(Moves.SPLASH, 0); + + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.HARSH_SUN); + }); + + it("should lift when enemy faints", async () => { + game.override + .battleType("single") + .moveset([ Moves.SHEER_COLD ]) + .ability(Abilities.NO_GUARD) + .startingLevel(100) + .enemyLevel(1) + .enemyMoveset([ Moves.SPLASH ]) + .enemySpecies(Species.MAGCARGO) + .enemyHasPassiveAbility(true); + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN); + + game.move.select(Moves.SHEER_COLD); + + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.HARSH_SUN); + }); + + it("should lift when pokemon returns upon switching from double to single battle", async () => { + game.override + .battleType("even-doubles") + .enemyMoveset([ Moves.SPLASH, Moves.MEMENTO ]) + .startingWave(12); + await game.classicMode.startBattle([ Species.MAGIKARP, Species.MAGCARGO ]); + + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN); + + game.move.select(Moves.SPLASH, 0, 2); + game.move.select(Moves.SPLASH, 1, 2); + await game.forceEnemyMove(Moves.MEMENTO, 0); + await game.forceEnemyMove(Moves.MEMENTO, 1); + + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN); + + await game.toNextWave(); + + expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.HARSH_SUN); + }); + + it("should lift when enemy is captured", async () => { + game.override + .battleType("single") + .enemyMoveset([ Moves.SPLASH ]) + .enemySpecies(Species.MAGCARGO) + .enemyHasPassiveAbility(true); + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN); + + game.scene.pokeballCounts[PokeballType.MASTER_BALL] = 1; + + game.doThrowPokeball(PokeballType.MASTER_BALL); + + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.HARSH_SUN); + }); +}); diff --git a/src/test/abilities/good_as_gold.test.ts b/src/test/abilities/good_as_gold.test.ts new file mode 100644 index 00000000000..ecda1a0e031 --- /dev/null +++ b/src/test/abilities/good_as_gold.test.ts @@ -0,0 +1,143 @@ +import { BattlerIndex } from "#app/battle"; +import { allAbilities } from "#app/data/ability"; +import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagType } from "#app/enums/arena-tag-type"; +import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { Stat } from "#app/enums/stat"; +import { StatusEffect } from "#app/enums/status-effect"; +import { WeatherType } from "#app/enums/weather-type"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Abilities - Good As Gold", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.GOOD_AS_GOLD) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should block normal status moves", async () => { + game.override.enemyMoveset( [ Moves.GROWL ] ); + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const player = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.SPLASH, 0); + + await game.phaseInterceptor.to("BerryPhase"); + + expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.GOOD_AS_GOLD); + expect(player.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should block memento and prevent the user from fainting", async () => { + game.override.enemyMoveset( [ Moves.MEMENTO ] ); + await game.classicMode.startBattle([ Species.MAGIKARP ]); + game.move.select(Moves.MEMENTO); + await game.phaseInterceptor.to("BerryPhase"); + expect(game.scene.getPlayerPokemon()!.isFainted()).toBe(false); + expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should not block any status moves that target the field, one side, or all pokemon", async () => { + game.override.battleType("double"); + game.override.enemyMoveset( [ Moves.STEALTH_ROCK, Moves.HAZE ] ); + game.override.moveset([ Moves.SWORDS_DANCE, Moves.SAFEGUARD ]); + await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS ]); + const [ good_as_gold, ball_fetch ] = game.scene.getPlayerField(); + + // Force second pokemon to have ball fetch to isolate to a single mon. + vi.spyOn(ball_fetch, "getAbility").mockReturnValue(allAbilities[Abilities.BALL_FETCH]); + + game.move.select(Moves.SWORDS_DANCE, 0); + game.move.select(Moves.SAFEGUARD, 1); + await game.forceEnemyMove(Moves.STEALTH_ROCK); + await game.forceEnemyMove(Moves.HAZE); + await game.setTurnOrder( [ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ] ); + await game.phaseInterceptor.to("BerryPhase"); + expect(good_as_gold.getAbility().id).toBe(Abilities.GOOD_AS_GOLD); + expect(good_as_gold.getStatStage(Stat.ATK)).toBe(0); + expect(game.scene.arena.getTagOnSide(ArenaTagType.STEALTH_ROCK, ArenaTagSide.PLAYER)).toBeDefined(); + expect(game.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, ArenaTagSide.PLAYER)).toBeDefined(); + }); + + it("should not block field targeted effects in singles", async () => { + game.override.battleType("single"); + game.override.enemyMoveset( [ Moves.SPIKES ] ); + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + game.move.select(Moves.SPLASH, 0); + await game.phaseInterceptor.to("BerryPhase"); + + expect(game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.PLAYER)).toBeDefined(); + }); + + it("should block the ally's helping hand", async () => { + game.override.battleType("double"); + game.override.moveset([ Moves.HELPING_HAND, Moves.TACKLE ]); + await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS ]); + + game.move.select(Moves.HELPING_HAND, 0); + game.move.select(Moves.TACKLE, 1); + await game.phaseInterceptor.to("MoveEndPhase", true); + + expect(game.scene.getPlayerField()[1].getTag(BattlerTagType.HELPING_HAND)).toBeUndefined(); + }); + + it("should block the ally's heal bell, but only if the good as gold user is on the field", async () => { + game.override.battleType("double"); + game.override.moveset([ Moves.HEAL_BELL, Moves.SPLASH ]); + game.override.statusEffect(StatusEffect.BURN); + await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS, Species.ABRA ]); + const [ good_as_gold, ball_fetch ] = game.scene.getPlayerField(); + + // Force second pokemon to have ball fetch to isolate to a single mon. + vi.spyOn(ball_fetch, "getAbility").mockReturnValue(allAbilities[Abilities.BALL_FETCH]); + + // turn 1 + game.move.select(Moves.SPLASH, 0); + game.move.select(Moves.HEAL_BELL, 1); + await game.toNextTurn(); + expect(good_as_gold.status?.effect).toBe(StatusEffect.BURN); + + game.doSwitchPokemon(2); + game.move.select(Moves.HEAL_BELL, 0); + await game.toNextTurn(); + expect(good_as_gold.status?.effect).toBeUndefined(); + }); + + it("should not block field targeted effects like rain dance", async () => { + game.override.battleType("single"); + game.override.enemyMoveset( [ Moves.RAIN_DANCE ] ); + game.override.weather(WeatherType.NONE); + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + game.move.select(Moves.SPLASH, 0); + await game.phaseInterceptor.to("BerryPhase"); + + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.RAIN); + }); +}); diff --git a/src/test/abilities/mirror_armor.test.ts b/src/test/abilities/mirror_armor.test.ts new file mode 100644 index 00000000000..070428a8ee7 --- /dev/null +++ b/src/test/abilities/mirror_armor.test.ts @@ -0,0 +1,315 @@ +import { Stat } from "#enums/stat"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { BattlerIndex } from "#app/battle"; + +// TODO: When Magic Bounce is implemented, make a test for its interaction with mirror guard, use screech + +describe("Ability - Mirror Armor", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override.battleType("single") + .enemySpecies(Species.RATTATA) + .enemyMoveset([ Moves.SPLASH, Moves.STICKY_WEB, Moves.TICKLE, Moves.OCTOLOCK ]) + .enemyAbility(Abilities.BALL_FETCH) + .startingLevel(2000) + .moveset([ Moves.SPLASH, Moves.STICKY_WEB, Moves.TICKLE, Moves.OCTOLOCK ]) + .ability(Abilities.BALL_FETCH); + }); + + it("Player side + single battle Intimidate - opponent loses stats", async () => { + game.override.ability(Abilities.MIRROR_ARMOR); + game.override.enemyAbility(Abilities.INTIMIDATE); + await game.classicMode.startBattle([ Species.BULBASAUR ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const userPokemon = game.scene.getPlayerPokemon()!; + + // Enemy has intimidate, enemy should lose -1 atk + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.toNextTurn(); + + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1); + expect(userPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("Enemy side + single battle Intimidate - player loses stats", async () => { + game.override.enemyAbility(Abilities.MIRROR_ARMOR); + game.override.ability(Abilities.INTIMIDATE); + await game.classicMode.startBattle([ Species.BULBASAUR ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const userPokemon = game.scene.getPlayerPokemon()!; + + // Enemy has intimidate, enemy should lose -1 atk + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.toNextTurn(); + + expect(userPokemon.getStatStage(Stat.ATK)).toBe(-1); + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("Player side + double battle Intimidate - opponents each lose -2 atk", async () => { + game.override.battleType("double"); + game.override.ability(Abilities.MIRROR_ARMOR); + game.override.enemyAbility(Abilities.INTIMIDATE); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER ]); + + const [ enemy1, enemy2 ] = game.scene.getEnemyField(); + const [ player1, player2 ] = game.scene.getPlayerField(); + + // Enemy has intimidate, enemy should lose -2 atk each + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.toNextTurn(); + + expect(enemy1.getStatStage(Stat.ATK)).toBe(-2); + expect(enemy2.getStatStage(Stat.ATK)).toBe(-2); + expect(player1.getStatStage(Stat.ATK)).toBe(0); + expect(player2.getStatStage(Stat.ATK)).toBe(0); + }); + + it("Enemy side + double battle Intimidate - players each lose -2 atk", async () => { + game.override.battleType("double"); + game.override.enemyAbility(Abilities.MIRROR_ARMOR); + game.override.ability(Abilities.INTIMIDATE); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER ]); + + const [ enemy1, enemy2 ] = game.scene.getEnemyField(); + const [ player1, player2 ] = game.scene.getPlayerField(); + + // Enemy has intimidate, enemy should lose -1 atk + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.toNextTurn(); + + expect(enemy1.getStatStage(Stat.ATK)).toBe(0); + expect(enemy2.getStatStage(Stat.ATK)).toBe(0); + expect(player1.getStatStage(Stat.ATK)).toBe(-2); + expect(player2.getStatStage(Stat.ATK)).toBe(-2); + }); + + it("Player side + single battle Intimidate + Tickle - opponent loses stats", async () => { + game.override.ability(Abilities.MIRROR_ARMOR); + game.override.enemyAbility(Abilities.INTIMIDATE); + await game.classicMode.startBattle([ Species.BULBASAUR ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const userPokemon = game.scene.getPlayerPokemon()!; + + // Enemy has intimidate and uses tickle, enemy receives -2 atk and -1 defense + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER); + await game.toNextTurn(); + + expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-2); + expect(userPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(userPokemon.getStatStage(Stat.DEF)).toBe(0); + }); + + it("Player side + double battle Intimidate + Tickle - opponents each lose -3 atk, -1 def", async () => { + game.override.battleType("double"); + game.override.ability(Abilities.MIRROR_ARMOR); + game.override.enemyAbility(Abilities.INTIMIDATE); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER ]); + + const [ enemy1, enemy2 ] = game.scene.getEnemyField(); + const [ player1, player2 ] = game.scene.getPlayerField(); + + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER_2); + await game.toNextTurn(); + + expect(player1.getStatStage(Stat.ATK)).toBe(0); + expect(player1.getStatStage(Stat.DEF)).toBe(0); + expect(player2.getStatStage(Stat.ATK)).toBe(0); + expect(player2.getStatStage(Stat.DEF)).toBe(0); + expect(enemy1.getStatStage(Stat.ATK)).toBe(-3); + expect(enemy1.getStatStage(Stat.DEF)).toBe(-1); + expect(enemy2.getStatStage(Stat.ATK)).toBe(-3); + expect(enemy2.getStatStage(Stat.DEF)).toBe(-1); + + }); + + it("Enemy side + single battle Intimidate + Tickle - player loses stats", async () => { + game.override.enemyAbility(Abilities.MIRROR_ARMOR); + game.override.ability(Abilities.INTIMIDATE); + await game.classicMode.startBattle([ Species.BULBASAUR ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const userPokemon = game.scene.getPlayerPokemon()!; + + // Enemy has intimidate and uses tickle, enemy receives -2 atk and -1 defense + game.move.select(Moves.TICKLE); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.toNextTurn(); + + expect(userPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(userPokemon.getStatStage(Stat.ATK)).toBe(-2); + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); + }); + + it("Player side + single battle Intimidate + oppoenent has white smoke - no one loses stats", async () => { + game.override.enemyAbility(Abilities.WHITE_SMOKE); + game.override.ability(Abilities.MIRROR_ARMOR); + await game.classicMode.startBattle([ Species.BULBASAUR ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const userPokemon = game.scene.getPlayerPokemon()!; + + // Enemy has intimidate and uses tickle, enemy has white smoke, no one loses stats + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER); + await game.toNextTurn(); + + expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(userPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(userPokemon.getStatStage(Stat.DEF)).toBe(0); + }); + + it("Enemy side + single battle Intimidate + player has white smoke - no one loses stats", async () => { + game.override.ability(Abilities.WHITE_SMOKE); + game.override.enemyAbility(Abilities.MIRROR_ARMOR); + await game.classicMode.startBattle([ Species.BULBASAUR ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const userPokemon = game.scene.getPlayerPokemon()!; + + // Enemy has intimidate and uses tickle, enemy has white smoke, no one loses stats + game.move.select(Moves.TICKLE); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.toNextTurn(); + + expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(userPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(userPokemon.getStatStage(Stat.DEF)).toBe(0); + }); + + it("Player side + single battle + opponent uses octolock - does not interact with mirror armor, player loses stats", async () => { + game.override.ability(Abilities.MIRROR_ARMOR); + await game.classicMode.startBattle([ Species.BULBASAUR ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const userPokemon = game.scene.getPlayerPokemon()!; + + // Enemy uses octolock, player loses stats at end of turn + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.OCTOLOCK, BattlerIndex.PLAYER); + await game.toNextTurn(); + + expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(0); + expect(userPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(userPokemon.getStatStage(Stat.SPDEF)).toBe(-1); + }); + + it("Enemy side + single battle + player uses octolock - does not interact with mirror armor, opponent loses stats", async () => { + game.override.enemyAbility(Abilities.MIRROR_ARMOR); + await game.classicMode.startBattle([ Species.BULBASAUR ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const userPokemon = game.scene.getPlayerPokemon()!; + + // Player uses octolock, enemy loses stats at end of turn + game.move.select(Moves.OCTOLOCK); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.toNextTurn(); + + expect(userPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(userPokemon.getStatStage(Stat.SPDEF)).toBe(0); + expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-1); + }); + + it("Both sides have mirror armor - does not loop, player loses attack", async () => { + game.override.enemyAbility(Abilities.MIRROR_ARMOR); + game.override.ability(Abilities.MIRROR_ARMOR); + game.override.ability(Abilities.INTIMIDATE); + await game.classicMode.startBattle([ Species.BULBASAUR ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const userPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.toNextTurn(); + + expect(userPokemon.getStatStage(Stat.ATK)).toBe(-1); + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("Single battle + sticky web applied player side - player switches out and enemy should lose -1 speed", async () => { + game.override.ability(Abilities.MIRROR_ARMOR); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const userPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.STICKY_WEB, BattlerIndex.PLAYER); + await game.toNextTurn(); + + game.doSwitchPokemon(1); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.toNextTurn(); + + expect(userPokemon.getStatStage(Stat.SPD)).toBe(0); + expect(enemyPokemon.getStatStage(Stat.SPD)).toBe(-1); + }); + + it("Double battle + sticky web applied player side - player switches out and enemy 1 should lose -1 speed", async () => { + game.override.battleType("double"); + game.override.ability(Abilities.MIRROR_ARMOR); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + const [ enemy1, enemy2 ] = game.scene.getEnemyField(); + const [ player1, player2 ] = game.scene.getPlayerField(); + + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.STICKY_WEB, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.toNextTurn(); + + game.doSwitchPokemon(2); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.toNextTurn(); + + expect(enemy1.getStatStage(Stat.SPD)).toBe(-1); + expect(enemy2.getStatStage(Stat.SPD)).toBe(0); + expect(player1.getStatStage(Stat.SPD)).toBe(0); + expect(player2.getStatStage(Stat.SPD)).toBe(0); + }); +}); diff --git a/src/test/abilities/shield_dust.test.ts b/src/test/abilities/shield_dust.test.ts index 9f1e6aeb11d..329f52cc4c6 100644 --- a/src/test/abilities/shield_dust.test.ts +++ b/src/test/abilities/shield_dust.test.ts @@ -53,11 +53,11 @@ describe("Abilities - Shield Dust", () => { expect(move.id).toBe(Moves.AIR_SLASH); const chance = new NumberHolder(move.chance); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false); - applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, phase.getFirstTarget()!, phase.getUserPokemon()!, null, null, false, chance); + await applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false); + await applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, phase.getFirstTarget()!, phase.getUserPokemon()!, null, null, false, chance); expect(chance.value).toBe(0); - }, 20000); + }); //TODO King's Rock Interaction Unit Test }); diff --git a/src/test/abilities/unseen_fist.test.ts b/src/test/abilities/unseen_fist.test.ts index f8fa8a723fe..584f997aa55 100644 --- a/src/test/abilities/unseen_fist.test.ts +++ b/src/test/abilities/unseen_fist.test.ts @@ -45,9 +45,9 @@ describe("Abilities - Unseen Fist", () => { it( "should not apply if the source has Long Reach", - () => { + async () => { game.override.passiveAbility(Abilities.LONG_REACH); - testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, false); + await testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, false); } ); @@ -67,7 +67,7 @@ describe("Abilities - Unseen Fist", () => { game.override.enemyLevel(1); game.override.moveset([ Moves.TACKLE ]); - await game.startBattle(); + await game.classicMode.startBattle(); const enemyPokemon = game.scene.getEnemyPokemon()!; enemyPokemon.addTag(BattlerTagType.SUBSTITUTE, 0, Moves.NONE, enemyPokemon.id); @@ -86,7 +86,7 @@ async function testUnseenFistHitResult(game: GameManager, attackMove: Moves, pro game.override.moveset([ attackMove ]); game.override.enemyMoveset([ protectMove, protectMove, protectMove, protectMove ]); - await game.startBattle(); + await game.classicMode.startBattle(); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); diff --git a/src/test/data/status_effect.test.ts b/src/test/data/status_effect.test.ts index 7948549b8e8..071dea989a9 100644 --- a/src/test/data/status_effect.test.ts +++ b/src/test/data/status_effect.test.ts @@ -20,8 +20,8 @@ const pokemonName = "PKM"; const sourceText = "SOURCE"; describe("Status Effect Messages", () => { - beforeAll(() => { - i18next.init(); + beforeAll(async () => { + await i18next.init(); }); describe("NONE", () => { diff --git a/src/test/eggs/egg.test.ts b/src/test/eggs/egg.test.ts index 3394771d84a..d7ed07dd151 100644 --- a/src/test/eggs/egg.test.ts +++ b/src/test/eggs/egg.test.ts @@ -14,7 +14,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite describe("Egg Generation Tests", () => { let phaserGame: Phaser.Game; let game: GameManager; - const EGG_HATCH_COUNT: integer = 1000; + const EGG_HATCH_COUNT: number = 1000; beforeAll(() => { phaserGame = new Phaser.Game({ diff --git a/src/test/eggs/manaphy-egg.test.ts b/src/test/eggs/manaphy-egg.test.ts index 4285476a913..5bb5b790201 100644 --- a/src/test/eggs/manaphy-egg.test.ts +++ b/src/test/eggs/manaphy-egg.test.ts @@ -9,7 +9,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite describe("Manaphy Eggs", () => { let phaserGame: Phaser.Game; let game: GameManager; - const EGG_HATCH_COUNT: integer = 48; + const EGG_HATCH_COUNT: number = 48; let rngSweepProgress: number = 0; beforeAll(() => { diff --git a/src/test/evolution.test.ts b/src/test/evolution.test.ts index 10748899d59..8dc19a548ca 100644 --- a/src/test/evolution.test.ts +++ b/src/test/evolution.test.ts @@ -40,10 +40,10 @@ describe("Evolution", () => { eevee.abilityIndex = 2; trapinch.abilityIndex = 2; - eevee.evolve(pokemonEvolutions[Species.EEVEE][6], eevee.getSpeciesForm()); + await eevee.evolve(pokemonEvolutions[Species.EEVEE][6], eevee.getSpeciesForm()); expect(eevee.abilityIndex).toBe(2); - trapinch.evolve(pokemonEvolutions[Species.TRAPINCH][0], trapinch.getSpeciesForm()); + await trapinch.evolve(pokemonEvolutions[Species.TRAPINCH][0], trapinch.getSpeciesForm()); expect(trapinch.abilityIndex).toBe(1); }); @@ -55,10 +55,10 @@ describe("Evolution", () => { bulbasaur.abilityIndex = 0; charmander.abilityIndex = 1; - bulbasaur.evolve(pokemonEvolutions[Species.BULBASAUR][0], bulbasaur.getSpeciesForm()); + await bulbasaur.evolve(pokemonEvolutions[Species.BULBASAUR][0], bulbasaur.getSpeciesForm()); expect(bulbasaur.abilityIndex).toBe(0); - charmander.evolve(pokemonEvolutions[Species.CHARMANDER][0], charmander.getSpeciesForm()); + await charmander.evolve(pokemonEvolutions[Species.CHARMANDER][0], charmander.getSpeciesForm()); expect(charmander.abilityIndex).toBe(1); }); @@ -68,7 +68,7 @@ describe("Evolution", () => { const squirtle = game.scene.getPlayerPokemon()!; squirtle.abilityIndex = 5; - squirtle.evolve(pokemonEvolutions[Species.SQUIRTLE][0], squirtle.getSpeciesForm()); + await squirtle.evolve(pokemonEvolutions[Species.SQUIRTLE][0], squirtle.getSpeciesForm()); expect(squirtle.abilityIndex).toBe(0); }); @@ -80,7 +80,7 @@ describe("Evolution", () => { nincada.metBiome = -1; nincada.gender = 1; - nincada.evolve(pokemonEvolutions[Species.NINCADA][0], nincada.getSpeciesForm()); + await nincada.evolve(pokemonEvolutions[Species.NINCADA][0], nincada.getSpeciesForm()); const ninjask = game.scene.getPlayerParty()[0]; const shedinja = game.scene.getPlayerParty()[1]; expect(ninjask.abilityIndex).toBe(2); @@ -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/items/light_ball.test.ts b/src/test/items/light_ball.test.ts index 987a5ab8b0c..aae1d806a28 100644 --- a/src/test/items/light_ball.test.ts +++ b/src/test/items/light_ball.test.ts @@ -31,7 +31,7 @@ describe("Items - Light Ball", () => { it("LIGHT_BALL activates in battle correctly", async() => { game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "LIGHT_BALL" }]); const consoleSpy = vi.spyOn(console, "log"); - await game.startBattle([ + await game.classicMode.startBattle([ Species.PIKACHU ]); @@ -64,7 +64,7 @@ describe("Items - Light Ball", () => { }); it("LIGHT_BALL held by PIKACHU", async() => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.PIKACHU ]); @@ -83,7 +83,7 @@ describe("Items - Light Ball", () => { expect(spAtkValue.value / spAtkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); @@ -92,7 +92,7 @@ describe("Items - Light Ball", () => { }, 20000); it("LIGHT_BALL held by fused PIKACHU (base)", async() => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.PIKACHU, Species.MAROWAK ]); @@ -122,7 +122,7 @@ describe("Items - Light Ball", () => { expect(spAtkValue.value / spAtkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); @@ -161,7 +161,7 @@ describe("Items - Light Ball", () => { expect(spAtkValue.value / spAtkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); @@ -189,7 +189,7 @@ describe("Items - Light Ball", () => { expect(spAtkValue.value / spAtkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); diff --git a/src/test/items/metal_powder.test.ts b/src/test/items/metal_powder.test.ts index 42ef9c1bb16..68c3107af08 100644 --- a/src/test/items/metal_powder.test.ts +++ b/src/test/items/metal_powder.test.ts @@ -31,7 +31,7 @@ describe("Items - Metal Powder", () => { it("METAL_POWDER activates in battle correctly", async() => { game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "METAL_POWDER" }]); const consoleSpy = vi.spyOn(console, "log"); - await game.startBattle([ + await game.classicMode.startBattle([ Species.DITTO ]); @@ -79,7 +79,7 @@ describe("Items - Metal Powder", () => { expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(2); @@ -112,7 +112,7 @@ describe("Items - Metal Powder", () => { expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(2); @@ -145,7 +145,7 @@ describe("Items - Metal Powder", () => { expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(2); @@ -167,7 +167,7 @@ describe("Items - Metal Powder", () => { expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(1); diff --git a/src/test/items/quick_powder.test.ts b/src/test/items/quick_powder.test.ts index d30111cbd6a..ae16daf17ff 100644 --- a/src/test/items/quick_powder.test.ts +++ b/src/test/items/quick_powder.test.ts @@ -31,7 +31,7 @@ describe("Items - Quick Powder", () => { it("QUICK_POWDER activates in battle correctly", async() => { game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "QUICK_POWDER" }]); const consoleSpy = vi.spyOn(console, "log"); - await game.startBattle([ + await game.classicMode.startBattle([ Species.DITTO ]); @@ -64,7 +64,7 @@ describe("Items - Quick Powder", () => { }); it("QUICK_POWDER held by DITTO", async() => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.DITTO ]); @@ -79,14 +79,14 @@ describe("Items - Quick Powder", () => { expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(2); - }, 20000); + }); it("QUICK_POWDER held by fused DITTO (base)", async() => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.DITTO, Species.MAROWAK ]); @@ -112,14 +112,14 @@ describe("Items - Quick Powder", () => { expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(2); - }, 20000); + }); it("QUICK_POWDER held by fused DITTO (part)", async() => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.MAROWAK, Species.DITTO ]); @@ -145,14 +145,14 @@ describe("Items - Quick Powder", () => { expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(2); - }, 20000); + }); it("QUICK_POWDER not held by DITTO", async() => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.MAROWAK ]); @@ -167,9 +167,9 @@ describe("Items - Quick Powder", () => { expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(1); - }, 20000); + }); }); diff --git a/src/test/items/thick_club.test.ts b/src/test/items/thick_club.test.ts index 08b19250ea7..d32c213e506 100644 --- a/src/test/items/thick_club.test.ts +++ b/src/test/items/thick_club.test.ts @@ -31,7 +31,7 @@ describe("Items - Thick Club", () => { it("THICK_CLUB activates in battle correctly", async() => { game.override.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "THICK_CLUB" }]); const consoleSpy = vi.spyOn(console, "log"); - await game.startBattle([ + await game.classicMode.startBattle([ Species.CUBONE ]); @@ -64,7 +64,7 @@ describe("Items - Thick Club", () => { }); it("THICK_CLUB held by CUBONE", async() => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.CUBONE ]); @@ -79,14 +79,14 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(2); - }, 20000); + }); it("THICK_CLUB held by MAROWAK", async() => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.MAROWAK ]); @@ -101,14 +101,14 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(2); - }, 20000); + }); it("THICK_CLUB held by ALOLA_MAROWAK", async() => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.ALOLA_MAROWAK ]); @@ -123,18 +123,18 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(2); - }, 20000); + }); it("THICK_CLUB held by fused CUBONE line (base)", async() => { // Randomly choose from the Cubone line const species = [ Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK ]; const randSpecies = Utils.randInt(species.length); - await game.startBattle([ + await game.classicMode.startBattle([ species[randSpecies], Species.PIKACHU ]); @@ -160,18 +160,18 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(2); - }, 20000); + }); it("THICK_CLUB held by fused CUBONE line (part)", async() => { // Randomly choose from the Cubone line const species = [ Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK ]; const randSpecies = Utils.randInt(species.length); - await game.startBattle([ + await game.classicMode.startBattle([ Species.PIKACHU, species[randSpecies] ]); @@ -197,14 +197,14 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(2); - }, 20000); + }); it("THICK_CLUB not held by CUBONE", async() => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.PIKACHU ]); @@ -219,9 +219,9 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + await game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); - }, 20000); + }); }); diff --git a/src/test/moves/dragon_rage.test.ts b/src/test/moves/dragon_rage.test.ts index a2350960546..61630ede326 100644 --- a/src/test/moves/dragon_rage.test.ts +++ b/src/test/moves/dragon_rage.test.ts @@ -45,14 +45,10 @@ describe("Moves - Dragon Rage", () => { game.override.enemyPassiveAbility(Abilities.BALL_FETCH); game.override.enemyLevel(100); - await game.startBattle(); + await game.classicMode.startBattle(); partyPokemon = game.scene.getPlayerParty()[0]; enemyPokemon = game.scene.getEnemyPokemon()!; - - // remove berries - game.scene.removePartyMemberModifiers(0); - game.scene.clearEnemyHeldItemModifiers(); }); it("ignores weaknesses", async () => { 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/moves/fissure.test.ts b/src/test/moves/fissure.test.ts index 0975a87b2b1..65719df0205 100644 --- a/src/test/moves/fissure.test.ts +++ b/src/test/moves/fissure.test.ts @@ -41,14 +41,10 @@ describe("Moves - Fissure", () => { game.override.enemyPassiveAbility(Abilities.BALL_FETCH); game.override.enemyLevel(100); - await game.startBattle(); + await game.classicMode.startBattle(); partyPokemon = game.scene.getPlayerParty()[0]; enemyPokemon = game.scene.getEnemyPokemon()!; - - // remove berries - game.scene.removePartyMemberModifiers(0); - game.scene.clearEnemyHeldItemModifiers(); }); it("ignores damage modification from abilities, for example FUR_COAT", async () => { diff --git a/src/test/moves/instruct.test.ts b/src/test/moves/instruct.test.ts index 13f0c83c784..b26f9c9669f 100644 --- a/src/test/moves/instruct.test.ts +++ b/src/test/moves/instruct.test.ts @@ -221,7 +221,8 @@ describe("Moves - Instruct", () => { it("should allow for dancer copying of instructed dance move", async () => { game.override .battleType("double") - .enemyMoveset([ Moves.INSTRUCT, Moves.SPLASH ]); + .enemyMoveset([ Moves.INSTRUCT, Moves.SPLASH ]) + .enemyLevel(1000); await game.classicMode.startBattle([ Species.ORICORIO, Species.VOLCARONA ]); const [ oricorio, volcarona ] = game.scene.getPlayerField(); @@ -236,11 +237,9 @@ describe("Moves - Instruct", () => { await game.phaseInterceptor.to("BerryPhase"); // fiery dance triggered dancer successfully for a total of 4 hits - // Volcarona fiery dance has a _small_ chance to 3HKO a shuckle in worst case, so we add the hit count of both - // foes to account for spillover + // Enemy level is set to a high value so that it does not faint even after all 4 hits instructSuccess(volcarona, Moves.FIERY_DANCE); - expect(game.scene.getEnemyField()[0].turnData.attacksReceived.length + - game.scene.getEnemyField()[1].turnData.attacksReceived.length).toBe(4); + expect(game.scene.getEnemyField()[0].turnData.attacksReceived.length).toBe(4); }); it("should not repeat move when switching out", async () => { diff --git a/src/test/moves/telekinesis.test.ts b/src/test/moves/telekinesis.test.ts index 76c0d001f00..ba2bc40a189 100644 --- a/src/test/moves/telekinesis.test.ts +++ b/src/test/moves/telekinesis.test.ts @@ -7,6 +7,7 @@ import { MoveResult } from "#app/field/pokemon"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; +import { BattlerIndex } from "#app/battle"; describe("Moves - Telekinesis", () => { let phaserGame: Phaser.Game; @@ -121,4 +122,17 @@ describe("Moves - Telekinesis", () => { expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeUndefined(); expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); }); + + it("should not be baton passed onto a mega gengar", async () => { + game.override.moveset([ Moves.BATON_PASS ]) + .enemyMoveset([ Moves.TELEKINESIS ]) + .starterForms({ [Species.GENGAR]: 1 }); + + await game.classicMode.startBattle([ Species.MAGIKARP, Species.GENGAR ]); + game.move.select(Moves.BATON_PASS); + game.doSelectPartyPokemon(1); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase"); + expect(game.scene.getPlayerPokemon()!.getTag(BattlerTagType.TELEKINESIS)).toBeUndefined(); + }); }); diff --git a/src/test/moves/toxic_spikes.test.ts b/src/test/moves/toxic_spikes.test.ts index c2d1c5aaee8..8969289c2f2 100644 --- a/src/test/moves/toxic_spikes.test.ts +++ b/src/test/moves/toxic_spikes.test.ts @@ -132,7 +132,7 @@ describe("Moves - Toxic Spikes", () => { const sessionData : SessionSaveData = gameData["getSessionSaveData"](); localStorage.setItem("sessionTestData", encrypt(JSON.stringify(sessionData), true)); const recoveredData : SessionSaveData = gameData.parseSessionData(decrypt(localStorage.getItem("sessionTestData")!, true)); - gameData.loadSession(0, recoveredData); + await gameData.loadSession(0, recoveredData); expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags); localStorage.removeItem("sessionTestData"); diff --git a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index ae52ffc0fef..f105678e71f 100644 --- a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -372,7 +372,7 @@ describe("Clowning Around - Mystery Encounter", () => { }); }); -async function addItemToPokemon(scene: BattleScene, pokemon: Pokemon, stackCount: integer, itemType: PokemonHeldItemModifierType) { +async function addItemToPokemon(scene: BattleScene, pokemon: Pokemon, stackCount: number, itemType: PokemonHeldItemModifierType) { const itemMod = itemType.newModifier(pokemon) as PokemonHeldItemModifier; itemMod.stackCount = stackCount; await scene.addModifier(itemMod, true, false, false, true); diff --git a/src/test/mystery-encounter/mystery-encounter-utils.test.ts b/src/test/mystery-encounter/mystery-encounter-utils.test.ts index f0057fea7f0..7c924b86e0d 100644 --- a/src/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/src/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -48,12 +48,12 @@ describe("Mystery Encounter Utils", () => { expect(result.species.speciesId).toBe(Species.ARCEUS); }); - it("gets a fainted pokemon from player party if isAllowedInBattle is false", () => { + it("gets a fainted pokemon from player party if isAllowedInBattle is false", async () => { // Both pokemon fainted scene.getPlayerParty().forEach(p => { p.hp = 0; p.trySetStatus(StatusEffect.FAINT); - p.updateInfo(); + void p.updateInfo(); }); // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) @@ -68,12 +68,12 @@ describe("Mystery Encounter Utils", () => { expect(result.species.speciesId).toBe(Species.ARCEUS); }); - it("gets an unfainted legal pokemon from player party if isAllowed is true and isFainted is false", () => { + it("gets an unfainted legal pokemon from player party if isAllowed is true and isFainted is false", async () => { // Only faint 1st pokemon const party = scene.getPlayerParty(); party[0].hp = 0; party[0].trySetStatus(StatusEffect.FAINT); - party[0].updateInfo(); + await party[0].updateInfo(); // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) game.override.seed("random"); @@ -87,12 +87,12 @@ describe("Mystery Encounter Utils", () => { expect(result.species.speciesId).toBe(Species.MANAPHY); }); - it("returns last unfainted pokemon if doNotReturnLastAbleMon is false", () => { + it("returns last unfainted pokemon if doNotReturnLastAbleMon is false", async () => { // Only faint 1st pokemon const party = scene.getPlayerParty(); party[0].hp = 0; party[0].trySetStatus(StatusEffect.FAINT); - party[0].updateInfo(); + await party[0].updateInfo(); // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) game.override.seed("random"); @@ -106,12 +106,12 @@ describe("Mystery Encounter Utils", () => { expect(result.species.speciesId).toBe(Species.MANAPHY); }); - it("never returns last unfainted pokemon if doNotReturnLastAbleMon is true", () => { + it("never returns last unfainted pokemon if doNotReturnLastAbleMon is true", async () => { // Only faint 1st pokemon const party = scene.getPlayerParty(); party[0].hp = 0; party[0].trySetStatus(StatusEffect.FAINT); - party[0].updateInfo(); + await party[0].updateInfo(); // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) game.override.seed("random"); @@ -152,12 +152,12 @@ describe("Mystery Encounter Utils", () => { expect(result.species.speciesId).toBe(Species.ARCEUS); }); - it("returns highest level unfainted if unfainted is true", () => { + it("returns highest level unfainted if unfainted is true", async () => { const party = scene.getPlayerParty(); party[0].level = 100; party[0].hp = 0; party[0].trySetStatus(StatusEffect.FAINT); - party[0].updateInfo(); + await party[0].updateInfo(); party[1].level = 10; const result = getHighestLevelPlayerPokemon(true); @@ -191,12 +191,12 @@ describe("Mystery Encounter Utils", () => { expect(result.species.speciesId).toBe(Species.ARCEUS); }); - it("returns lowest level unfainted if unfainted is true", () => { + it("returns lowest level unfainted if unfainted is true", async () => { const party = scene.getPlayerParty(); party[0].level = 10; party[0].hp = 0; party[0].trySetStatus(StatusEffect.FAINT); - party[0].updateInfo(); + await party[0].updateInfo(); party[1].level = 100; const result = getLowestLevelPlayerPokemon(true); 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/test/ui/transfer-item.test.ts b/src/test/ui/transfer-item.test.ts index 762db7fc7ce..b08b056f60e 100644 --- a/src/test/ui/transfer-item.test.ts +++ b/src/test/ui/transfer-item.test.ts @@ -2,8 +2,6 @@ import { BerryType } from "#app/enums/berry-type"; import { Button } from "#app/enums/buttons"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; -import { BattleEndPhase } from "#app/phases/battle-end-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; @@ -12,7 +10,6 @@ import Phaser from "phaser"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; - describe("UI - Transfer Items", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -41,7 +38,7 @@ describe("UI - Transfer Items", () => { game.override.enemySpecies(Species.MAGIKARP); game.override.enemyMoveset([ Moves.SPLASH ]); - await game.startBattle([ Species.RAYQUAZA, Species.RAYQUAZA, Species.RAYQUAZA ]); + await game.classicMode.startBattle([ Species.RAYQUAZA, Species.RAYQUAZA, Species.RAYQUAZA ]); game.move.select(Moves.DRAGON_CLAW); @@ -52,10 +49,10 @@ describe("UI - Transfer Items", () => { handler.setCursor(1); handler.processInput(Button.ACTION); - game.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER); + void game.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER); }); - await game.phaseInterceptor.to(BattleEndPhase); + await game.phaseInterceptor.to("BattleEndPhase"); }); it("check red tint for held item limit in transfer menu", async () => { @@ -72,7 +69,7 @@ describe("UI - Transfer Items", () => { game.phaseInterceptor.unlock(); }); - await game.phaseInterceptor.to(SelectModifierPhase); + await game.phaseInterceptor.to("SelectModifierPhase"); }, 20000); it("check transfer option for pokemon to transfer to", async () => { @@ -91,6 +88,6 @@ describe("UI - Transfer Items", () => { game.phaseInterceptor.unlock(); }); - await game.phaseInterceptor.to(SelectModifierPhase); + await game.phaseInterceptor.to("SelectModifierPhase"); }, 20000); }); diff --git a/src/test/utils/TextInterceptor.ts b/src/test/utils/TextInterceptor.ts index 466bcbf8052..089d8967c61 100644 --- a/src/test/utils/TextInterceptor.ts +++ b/src/test/utils/TextInterceptor.ts @@ -9,12 +9,12 @@ export default class TextInterceptor { scene.messageWrapper = this; } - showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer): void { + showText(text: string, delay?: number, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number): void { console.log(text); this.logs.push(text); } - showDialogue(text: string, name: string, delay?: integer, callback?: Function, callbackDelay?: integer, promptDelay?: integer): void { + showDialogue(text: string, name: string, delay?: number, callback?: Function, callbackDelay?: number, promptDelay?: number): void { console.log(name, text); this.logs.push(name, text); } diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index 8e595212898..b2015700c9b 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -24,6 +24,7 @@ import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { TurnStartPhase } from "#app/phases/turn-start-phase"; import ErrorInterceptor from "#app/test/utils/errorInterceptor"; import type InputsHandler from "#app/test/utils/inputsHandler"; +import type BallUiHandler from "#app/ui/ball-ui-handler"; import type BattleMessageUiHandler from "#app/ui/battle-message-ui-handler"; import type CommandUiHandler from "#app/ui/command-ui-handler"; import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; @@ -255,7 +256,7 @@ export default class GameManager { * @param {BattlerIndex} targetIndex The index of the attack target, or `undefined` for multi-target attacks * @param movePosition The index of the move in the pokemon's moveset array */ - selectTarget(movePosition: integer, targetIndex?: BattlerIndex) { + selectTarget(movePosition: number, targetIndex?: BattlerIndex) { this.onNextPrompt("SelectTargetPhase", Mode.TARGET_SELECT, () => { const handler = this.scene.ui.getHandler() as TargetSelectUiHandler; const move = (this.scene.getCurrentPhase() as SelectTargetPhase).getPokemon().getMoveset()[movePosition]!.getMove(); // TODO: is the bang correct? @@ -387,7 +388,7 @@ export default class GameManager { * @param path - The path to the data file. * @returns A promise that resolves with a tuple containing a boolean indicating success and an integer status code. */ - async importData(path): Promise<[boolean, integer]> { + async importData(path): Promise<[boolean, number]> { const saveKey = "x0i2O7WRiANTqPmZ"; const dataRaw = fs.readFileSync(path, { encoding: "utf8", flag: "r" }); let dataStr = AES.decrypt(dataRaw, saveKey).toString(enc.Utf8); @@ -458,6 +459,24 @@ export default class GameManager { }); } + /** + * Select the BALL option from the command menu, then press Action; in the BALL + * menu, select a pokéball type and press Action again to throw it. + * @param ballIndex the index of the pokeball to throw + */ + public doThrowPokeball(ballIndex: number) { + this.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + (this.scene.ui.getHandler() as CommandUiHandler).setCursor(1); + (this.scene.ui.getHandler() as CommandUiHandler).processInput(Button.ACTION); + }); + + this.onNextPrompt("CommandPhase", Mode.BALL, () => { + const ballHandler = this.scene.ui.getHandler() as BallUiHandler; + ballHandler.setCursor(ballIndex); + ballHandler.processInput(Button.ACTION); // select ball and throw + }); + } + /** * Intercepts `TurnStartPhase` and mocks {@linkcode TurnStartPhase.getSpeedOrder}'s return value. * Used to manually modify Pokemon turn order. diff --git a/src/test/utils/helpers/moveHelper.ts b/src/test/utils/helpers/moveHelper.ts index ad39755b556..ee026c06a8d 100644 --- a/src/test/utils/helpers/moveHelper.ts +++ b/src/test/utils/helpers/moveHelper.ts @@ -102,7 +102,7 @@ export class MoveHelper extends GameManagerHelper { * defaults to 0 (first slot) and 4 aborts the procedure * @returns a promise that resolves once the move has been successfully learnt */ - public async learnMove(move: Moves | integer, partyIndex: integer = 0, moveSlotIndex: integer = 0) { + public async learnMove(move: Moves | number, partyIndex: number = 0, moveSlotIndex: number = 0) { return new Promise(async (resolve, reject) => { this.game.scene.pushPhase(new LearnMovePhase(partyIndex, move)); diff --git a/src/test/utils/helpers/overridesHelper.ts b/src/test/utils/helpers/overridesHelper.ts index 822c42163b1..47358738048 100644 --- a/src/test/utils/helpers/overridesHelper.ts +++ b/src/test/utils/helpers/overridesHelper.ts @@ -181,6 +181,20 @@ export class OverridesHelper extends GameManagerHelper { return this; } + /** + * Forces the status of the player (pokemon) **passive** {@linkcode Abilities | ability} + * @param hasPassiveAbility forces the passive to be active if `true`, inactive if `false` + * @returns `this` + */ + public hasPassiveAbility(hasPassiveAbility: boolean | null): this { + vi.spyOn(Overrides, "HAS_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(hasPassiveAbility); + if (hasPassiveAbility === null) { + this.log("Player Pokemon PASSIVE ability no longer force enabled or disabled!"); + } else { + this.log(`Player Pokemon PASSIVE ability is force ${hasPassiveAbility ? "enabled" : "disabled"}!`); + } + return this; + } /** * Override the player (pokemon) {@linkcode Moves | moves}set * @param moveset the {@linkcode Moves | moves}set to set @@ -325,6 +339,21 @@ export class OverridesHelper extends GameManagerHelper { return this; } + /** + * Forces the status of the enemy (pokemon) **passive** {@linkcode Abilities | ability} + * @param hasPassiveAbility forces the passive to be active if `true`, inactive if `false` + * @returns `this` + */ + public enemyHasPassiveAbility(hasPassiveAbility: boolean | null): this { + vi.spyOn(Overrides, "OPP_HAS_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(hasPassiveAbility); + if (hasPassiveAbility === null) { + this.log("Enemy Pokemon PASSIVE ability no longer force enabled or disabled!"); + } else { + this.log(`Enemy Pokemon PASSIVE ability is force ${hasPassiveAbility ? "enabled" : "disabled"}!`); + } + return this; + } + /** * Override the enemy (pokemon) {@linkcode Moves | moves}set * @param moveset the {@linkcode Moves | moves}set to set diff --git a/src/test/utils/inputsHandler.ts b/src/test/utils/inputsHandler.ts index 8b8a89e45dc..e2591def447 100644 --- a/src/test/utils/inputsHandler.ts +++ b/src/test/utils/inputsHandler.ts @@ -30,7 +30,7 @@ export default class InputsHandler { this.init(); } - pressTouch(button: string, duration: integer): Promise { + pressTouch(button: string, duration: number): Promise { return new Promise(async (resolve) => { this.fakeMobile.touchDown(button); await holdOn(duration); @@ -39,7 +39,7 @@ export default class InputsHandler { }); } - pressGamepadButton(button: integer, duration: integer): Promise { + pressGamepadButton(button: number, duration: number): Promise { return new Promise(async (resolve) => { this.scene.input.gamepad?.emit("down", this.fakePad, { index: button }); await holdOn(duration); @@ -48,7 +48,7 @@ export default class InputsHandler { }); } - pressKeyboardKey(key: integer, duration: integer): Promise { + pressKeyboardKey(key: number, duration: number): Promise { return new Promise(async (resolve) => { this.scene.input.keyboard?.emit("keydown", { keyCode: key }); await holdOn(duration); diff --git a/src/test/utils/mocks/mocksContainer/mockText.ts b/src/test/utils/mocks/mocksContainer/mockText.ts index 604679af372..5550e801386 100644 --- a/src/test/utils/mocks/mocksContainer/mockText.ts +++ b/src/test/utils/mocks/mocksContainer/mockText.ts @@ -81,11 +81,11 @@ export default class MockText implements MockGameObject { showText( text: string, - delay?: integer | null, + delay?: number | null, callback?: Function | null, - callbackDelay?: integer | null, + callbackDelay?: number | null, prompt?: boolean | null, - promptDelay?: integer | null + promptDelay?: number | null ) { this.scene.messageWrapper.showText(text, delay, callback, callbackDelay, prompt, promptDelay); if (callback) { @@ -93,7 +93,7 @@ export default class MockText implements MockGameObject { } } - showDialogue(keyOrText: string, name: string | undefined, delay: integer | null = 0, callback: Function, callbackDelay?: integer, promptDelay?: integer) { + showDialogue(keyOrText: string, name: string | undefined, delay: number | null = 0, callback: Function, callbackDelay?: number, promptDelay?: number) { this.scene.messageWrapper.showDialogue(keyOrText, name, delay, callback, callbackDelay, promptDelay); if (callback) { callback(); diff --git a/src/test/utils/testUtils.ts b/src/test/utils/testUtils.ts index a8410f8ba40..40e4bbe8775 100644 --- a/src/test/utils/testUtils.ts +++ b/src/test/utils/testUtils.ts @@ -18,7 +18,7 @@ export function mockI18next() { * @param end end number e.g. 10 * @returns an array of numbers */ -export function arrayOfRange(start: integer, end: integer) { +export function arrayOfRange(start: number, end: number) { return Array.from({ length: end - start }, (_v, k) => k + start); } diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 7a9f0e59993..bebacf87ebc 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -27,6 +27,7 @@ interface EventBanner { interface EventEncounter { species: Species; blockEvolution?: boolean; + formIndex?: number; } interface EventMysteryEncounterTier { @@ -49,6 +50,7 @@ interface TimedEvent extends EventBanner { weather?: WeatherPoolEntry[]; mysteryEncounterTierChanges?: EventMysteryEncounterTier[]; luckBoostedSpecies?: Species[]; + boostFusions?: boolean; //MODIFIER REWORK PLEASE } const timedEvents: TimedEvent[] = [ @@ -144,6 +146,40 @@ const timedEvents: TimedEvent[] = [ Species.ROARING_MOON, Species.BLOODMOON_URSALUNA ] + }, + { + name: "Valentine", + eventType: EventType.SHINY, + startDate: new Date(Date.UTC(2025, 1, 10)), + endDate: new Date(Date.UTC(2025, 1, 21)), + boostFusions: true, + shinyMultiplier: 2, + bannerKey: "valentines2025event-", + scale: 0.21, + availableLangs: [ "en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN" ], + eventEncounters: [ + { species: Species.NIDORAN_F }, + { species: Species.NIDORAN_M }, + { species: Species.IGGLYBUFF }, + { species: Species.SMOOCHUM }, + { species: Species.VOLBEAT }, + { species: Species.ILLUMISE }, + { species: Species.ROSELIA }, + { species: Species.LUVDISC }, + { species: Species.WOOBAT }, + { species: Species.FRILLISH }, + { species: Species.ALOMOMOLA }, + { species: Species.FURFROU, formIndex: 1 }, // Heart trim + { species: Species.ESPURR }, + { species: Species.SPRITZEE }, + { species: Species.SWIRLIX }, + { species: Species.APPLIN }, + { species: Species.MILCERY }, + { species: Species.INDEEDEE }, + { species: Species.TANDEMAUS }, + { species: Species.ENAMORUS } + ], + luckBoostedSpecies: [ Species.LUVDISC ] } ]; @@ -297,6 +333,10 @@ export class TimedEventManager { }); return ret; } + + areFusionsBoosted(): boolean { + return timedEvents.some((te) => this.isActive(te) && te.boostFusions); + } } export class TimedEventDisplay extends Phaser.GameObjects.Container { 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 df592fc45b1..1840792e667 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -1,18 +1,19 @@ 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; yOffset?: number; options: OptionSelectItem[]; - maxOptions?: integer; - delay?: integer; + maxOptions?: number; + delay?: number; noCancel?: boolean; supportHover?: boolean; } @@ -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,26 +36,32 @@ 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; protected blockInput: boolean; - protected scrollCursor: integer = 0; + 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); } - abstract getWindowWidth(): integer; + abstract getWindowWidth(): number; - getWindowHeight(): integer { + getWindowHeight(): number { return (Math.min((this.config?.options || []).length, this.config?.maxOptions || 99) + 1) * 96 * this.scale; } @@ -79,46 +88,56 @@ 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: integer) => { + ); + + options.forEach((option: OptionSelectItem, i: number) => { if (option.item) { const itemIcon = globalScene.add.sprite(0, 0, "items", option.item); itemIcon.setScale(3 * this.scale); @@ -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: integer): 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/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index ff1e2ee9184..74a121c231b 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -302,7 +302,7 @@ export default class AchvsUiHandler extends MessageUiHandler { return success; } - setCursor(cursor: integer, pageChange?: boolean): boolean { + setCursor(cursor: number, pageChange?: boolean): boolean { const ret = super.setCursor(cursor); let update = ret; @@ -340,12 +340,12 @@ export default class AchvsUiHandler extends MessageUiHandler { } /** - * setScrollCursor(scrollCursor: integer) : boolean + * setScrollCursor(scrollCursor: number) : boolean * scrollCursor refers to the page's position within the entire sum of the data, unlike cursor, which refers to a user's position within displayed data - * @param takes a scrollCursor that has been updated based on user behavior + * @param scrollCursor takes a value that has been updated based on user behavior * @returns returns a boolean that indicates whether the updated scrollCursor led to an update in the data displayed. */ - setScrollCursor(scrollCursor: integer): boolean { + setScrollCursor(scrollCursor: number): boolean { if (scrollCursor === this.scrollCursor) { return false; } @@ -391,7 +391,7 @@ export default class AchvsUiHandler extends MessageUiHandler { const achvRange = Object.values(achvs).slice(itemOffset, itemLimit + itemOffset); - achvRange.forEach((achv: Achv, i: integer) => { + achvRange.forEach((achv: Achv, i: number) => { const icon = this.icons[i]; const unlocked = achvUnlocks.hasOwnProperty(achv.id); const hidden = !unlocked && achv.secret && (!achv.parentId || !achvUnlocks.hasOwnProperty(achv.parentId)); @@ -431,7 +431,7 @@ export default class AchvsUiHandler extends MessageUiHandler { const voucherRange = Object.values(vouchers).slice(itemOffset, itemLimit + itemOffset); - voucherRange.forEach((voucher: Voucher, i: integer) => { + voucherRange.forEach((voucher: Voucher, i: number) => { const icon = this.icons[i]; const unlocked = voucherUnlocks.hasOwnProperty(voucher.id); diff --git a/src/ui/autocomplete-ui-handler.ts b/src/ui/autocomplete-ui-handler.ts index 8754e65db77..23abdb85772 100644 --- a/src/ui/autocomplete-ui-handler.ts +++ b/src/ui/autocomplete-ui-handler.ts @@ -8,7 +8,7 @@ export default class AutoCompleteUiHandler extends AbstractOptionSelectUiHandler super(mode); } - getWindowWidth(): integer { + getWindowWidth(): number { return 64; } diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index 2c1a53a9a52..a402d11ef1d 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -110,7 +110,7 @@ export default class BallUiHandler extends UiHandler { this.countsText.setText(Object.values(globalScene.pokeballCounts).map(c => `x${c}`).join("\n")); } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { const ret = super.setCursor(cursor); if (!this.cursorObj) { 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/battle-info.ts b/src/ui/battle-info.ts index ca98d4c9d10..fa8767f5eb0 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -22,17 +22,17 @@ export default class BattleInfo extends Phaser.GameObjects.Container { private player: boolean; private mini: boolean; private boss: boolean; - private bossSegments: integer; + private bossSegments: number; private offset: boolean; private lastName: string | null; private lastTeraType: Type; private lastStatus: StatusEffect; - private lastHp: integer; - private lastMaxHp: integer; + private lastHp: number; + private lastMaxHp: number; private lastHpFrame: string | null; - private lastExp: integer; - private lastLevelExp: integer; - private lastLevel: integer; + private lastExp: number; + private lastLevelExp: number; + private lastLevel: number; private lastLevelCapped: boolean; private lastStats: string; @@ -739,7 +739,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { }); } - setLevel(level: integer): void { + setLevel(level: number): void { const isCapped = level >= globalScene.getMaxExpLevel(); this.levelNumbersContainer.removeAll(true); const levelStr = level.toString(); @@ -749,7 +749,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.levelContainer.setX((this.player ? -41 : -50) - 8 * Math.max(levelStr.length - 3, 0)); } - setHpNumbers(hp: integer, maxHp: integer): void { + setHpNumbers(hp: number, maxHp: number): void { if (!this.player || !globalScene) { return; } @@ -766,7 +766,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { } } - updateStats(stats: integer[]): void { + updateStats(stats: number[]): void { this.statOrder.map((s, i) => { if (s !== Stat.HP) { this.statNumbers[i].setFrame(stats[s - 1].toString()); diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts index 2c0998b79ab..93de69240c3 100644 --- a/src/ui/battle-message-ui-handler.ts +++ b/src/ui/battle-message-ui-handler.ts @@ -154,19 +154,19 @@ export default class BattleMessageUiHandler extends MessageUiHandler { super.clear(); } - showText(text: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) { + showText(text: string, delay?: number | null, callback?: Function | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null) { this.hideNameText(); super.showText(text, delay, callback, callbackDelay, prompt, promptDelay); } - showDialogue(text: string, name?: string, delay?: integer | null, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) { + showDialogue(text: string, name?: string, delay?: number | null, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number) { if (name) { this.showNameText(name); } super.showDialogue(text, name, delay, callback, callbackDelay, prompt, promptDelay); } - promptLevelUpStats(partyMemberIndex: integer, prevStats: integer[], showTotals: boolean): Promise { + promptLevelUpStats(partyMemberIndex: number, prevStats: number[], showTotals: boolean): Promise { return new Promise(resolve => { if (!globalScene.showLevelUpStats) { return resolve(); @@ -191,7 +191,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler { }); } - promptIvs(pokemonId: integer, ivs: integer[], shownIvsCount: integer): Promise { + promptIvs(pokemonId: number, ivs: number[], shownIvsCount: number): Promise { return new Promise(resolve => { globalScene.executeWithSeedOffset(() => { let levelUpStatsValuesText = ""; @@ -211,7 +211,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler { }); } - getTopIvs(ivs: integer[], shownIvsCount: integer): Stat[] { + getTopIvs(ivs: number[], shownIvsCount: number): Stat[] { let shownStats: Stat[] = []; if (shownIvsCount < 6) { const statsPool = PERMANENT_STATS.slice(); @@ -226,7 +226,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler { return shownStats; } - getIvDescriptor(value: integer, typeIv: integer, pokemonId: integer): string { + getIvDescriptor(value: number, typeIv: number, pokemonId: number): string { const starterSpecies = globalScene.getPokemonById(pokemonId)!.species.getRootSpeciesId(); // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists const starterIvs: number[] = globalScene.gameData.dexData[starterSpecies].ivs; const uiTheme = globalScene.uiTheme; // Assuming uiTheme is accessible diff --git a/src/ui/candy-bar.ts b/src/ui/candy-bar.ts index 81478f6fa7c..d58fd040a7c 100644 --- a/src/ui/candy-bar.ts +++ b/src/ui/candy-bar.ts @@ -47,7 +47,7 @@ export default class CandyBar extends Phaser.GameObjects.Container { this.shown = false; } - showStarterSpeciesCandy(starterSpeciesId: Species, count: integer): Promise { + showStarterSpeciesCandy(starterSpeciesId: Species, count: number): Promise { return new Promise(resolve => { if (this.shown) { if (this.speciesId === starterSpeciesId) { diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index 9c13d54bf55..31ee91388fc 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -20,7 +20,7 @@ export default class GameChallengesUiHandler extends UiHandler { private challengesContainer: Phaser.GameObjects.Container; private valuesContainer: Phaser.GameObjects.Container; - private scrollCursor: integer; + private scrollCursor: number; private optionsBg: Phaser.GameObjects.NineSlice; @@ -430,7 +430,7 @@ export default class GameChallengesUiHandler extends UiHandler { return success; } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { let ret = super.setCursor(cursor); if (!this.cursorObj) { @@ -447,7 +447,7 @@ export default class GameChallengesUiHandler extends UiHandler { return ret; } - setScrollCursor(scrollCursor: integer): boolean { + setScrollCursor(scrollCursor: number): boolean { if (scrollCursor === this.scrollCursor) { return false; } diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 32a3bb764a9..de75f29ff6f 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -19,8 +19,8 @@ export default class CommandUiHandler extends UiHandler { private commandsContainer: Phaser.GameObjects.Container; private cursorObj: Phaser.GameObjects.Image | null; - protected fieldIndex: integer = 0; - protected cursor2: integer = 0; + protected fieldIndex: number = 0; + protected cursor2: number = 0; constructor() { super(Mode.COMMAND); @@ -50,7 +50,7 @@ export default class CommandUiHandler extends UiHandler { show(args: any[]): boolean { super.show(args); - this.fieldIndex = args.length ? args[0] as integer : 0; + this.fieldIndex = args.length ? args[0] as number : 0; this.commandsContainer.setVisible(true); @@ -144,11 +144,11 @@ export default class CommandUiHandler extends UiHandler { return success; } - getCursor(): integer { + getCursor(): number { return !this.fieldIndex ? this.cursor : this.cursor2; } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { const changed = this.getCursor() !== cursor; if (changed) { if (!this.fieldIndex) { diff --git a/src/ui/confirm-ui-handler.ts b/src/ui/confirm-ui-handler.ts index b53a350cce0..3a3a5dfbfe7 100644 --- a/src/ui/confirm-ui-handler.ts +++ b/src/ui/confirm-ui-handler.ts @@ -8,16 +8,16 @@ import { globalScene } from "#app/global-scene"; export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { - public static readonly windowWidth: integer = 48; + public static readonly windowWidth: number = 48; private switchCheck: boolean; - private switchCheckCursor: integer; + private switchCheckCursor: number; constructor() { super(Mode.CONFIRM); } - getWindowWidth(): integer { + getWindowWidth(): number { return ConfirmUiHandler.windowWidth; } @@ -45,7 +45,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { } } ], - delay: args.length >= 8 && args[7] !== null ? args[7] as integer : 0 + delay: args.length >= 8 && args[7] !== null ? args[7] as number : 0 }; super.show([ config ]); @@ -105,7 +105,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { return super.processInput(button); } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { const ret = super.setCursor(cursor); if (ret && this.switchCheck) { diff --git a/src/ui/daily-run-scoreboard.ts b/src/ui/daily-run-scoreboard.ts index 0c7ac0d60b3..d9131150262 100644 --- a/src/ui/daily-run-scoreboard.ts +++ b/src/ui/daily-run-scoreboard.ts @@ -6,10 +6,10 @@ import { WindowVariant, addWindow } from "./ui-theme"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; export interface RankingEntry { - rank: integer, + rank: number, username: string, - score: integer, - wave: integer + score: number, + wave: number } // Don't forget to update translations when adding a new category @@ -28,8 +28,8 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { private pageNumberLabel: Phaser.GameObjects.Text; private nextPageButton: Phaser.GameObjects.Sprite; - private pageCount: integer; - private page: integer; + private pageCount: number; + private page: number; private category: ScoreboardCategory; private _isUpdating: boolean; @@ -157,7 +157,7 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { this.rankingsContainer.add(getEntry(i18next.t("menu:positionIcon"), i18next.t("menu:usernameScoreboard"), i18next.t("menu:score"), i18next.t("menu:wave"))); - rankings.forEach((r: RankingEntry, i: integer) => { + rankings.forEach((r: RankingEntry, i: number) => { const entryContainer = getEntry(r.rank.toString(), r.username, r.score.toString(), r.wave.toString()); entryContainer.setY((i + 1) * 9); this.rankingsContainer.add(entryContainer); @@ -176,7 +176,7 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { * @param {ScoreboardCategory} [category=this.category] - The category to fetch rankings for. Defaults to the current category. * @param {number} [page=this.page] - The page number to fetch. Defaults to the current page. */ - update(category: ScoreboardCategory = this.category, page: integer = this.page) { + update(category: ScoreboardCategory = this.category, page: number = this.page) { if (this.isUpdating) { return; } diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts index 8c318b29d64..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 { @@ -370,7 +407,12 @@ export class DropDown extends Phaser.GameObjects.Container { return this.setCursor(this.defaultCursor); } - setCursor(cursor: integer): boolean { + 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/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 82e361fac39..3cd8a7e8dc9 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -30,7 +30,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { private voucherCountLabels: Phaser.GameObjects.Text[]; - private gachaCursor: integer; + private gachaCursor: number; private cursorObj: Phaser.GameObjects.Image; private transitioning: boolean; @@ -331,14 +331,14 @@ export default class EggGachaUiHandler extends MessageUiHandler { return true; } - getDelayValue(delay: integer) { + getDelayValue(delay: number) { if (this.transitioning && this.transitionCancelled) { delay = Math.ceil(delay / 5); } return Utils.fixedInt(delay); } - pull(pullCount: integer = 0, count: integer = 0, eggs?: Egg[]): void { + pull(pullCount: number = 0, count: number = 0, eggs?: Egg[]): void { if (Overrides.EGG_GACHA_PULL_COUNT_OVERRIDE && !count) { pullCount = Overrides.EGG_GACHA_PULL_COUNT_OVERRIDE; } @@ -575,7 +575,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { } } - consumeVouchers(voucherType: VoucherType, count: integer): void { + consumeVouchers(voucherType: VoucherType, count: number): void { globalScene.gameData.voucherCounts[voucherType] = Math.max(globalScene.gameData.voucherCounts[voucherType] - count, 0); this.updateVoucherCounts(); } @@ -751,7 +751,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { return success || error; } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { const ret = super.setCursor(cursor); if (!this.cursorObj) { @@ -765,7 +765,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { return ret; } - setGachaCursor(cursor: integer): boolean { + setGachaCursor(cursor: number): boolean { const oldCursor = this.gachaCursor; const changed = oldCursor !== cursor; diff --git a/src/ui/egg-hatch-scene-handler.ts b/src/ui/egg-hatch-scene-handler.ts index 189d2f295d1..791c488c91a 100644 --- a/src/ui/egg-hatch-scene-handler.ts +++ b/src/ui/egg-hatch-scene-handler.ts @@ -54,7 +54,7 @@ export default class EggHatchSceneHandler extends UiHandler { return globalScene.ui.getMessageHandler().processInput(button); } - setCursor(_cursor: integer): boolean { + setCursor(_cursor: number): boolean { return false; } diff --git a/src/ui/evolution-scene-handler.ts b/src/ui/evolution-scene-handler.ts index e7866dfea53..b35aa8f6cc0 100644 --- a/src/ui/evolution-scene-handler.ts +++ b/src/ui/evolution-scene-handler.ts @@ -82,7 +82,7 @@ export default class EvolutionSceneHandler extends MessageUiHandler { return false; } - setCursor(_cursor: integer): boolean { + setCursor(_cursor: number): boolean { return false; } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 220e5d817ef..72fb90066e7 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -32,8 +32,8 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { private moveCategoryIcon: Phaser.GameObjects.Sprite; private moveInfoOverlay : MoveInfoOverlay; - protected fieldIndex: integer = 0; - protected cursor2: integer = 0; + protected fieldIndex: number = 0; + protected cursor2: number = 0; constructor() { super(Mode.FIGHT); @@ -113,7 +113,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { show(args: any[]): boolean { super.show(args); - this.fieldIndex = args.length ? args[0] as integer : 0; + this.fieldIndex = args.length ? args[0] as number : 0; const messageHandler = this.getUi().getMessageHandler(); messageHandler.bg.setVisible(false); @@ -206,11 +206,11 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { return this.active; } - getCursor(): integer { + getCursor(): number { return !this.fieldIndex ? this.cursor : this.cursor2; } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { const ui = this.getUi(); this.moveInfoOverlay.clear(); 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/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index 2fa5b54ac76..09fd178e101 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -372,7 +372,7 @@ export default class GameStatsUiHandler extends UiHandler { return success; } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { const ret = super.setCursor(cursor); if (ret) { diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index 1a1070122ab..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, @@ -171,7 +172,7 @@ export default class MenuUiHandler extends MessageUiHandler { const manageDataOptions: any[] = []; // TODO: proper type - const confirmSlot = (message: string, slotFilter: (i: integer) => boolean, callback: (i: integer) => void) => { + const confirmSlot = (message: string, slotFilter: (i: number) => boolean, callback: (i: number) => void) => { ui.revertMode(); ui.showText(message, null, () => { const config: OptionSelectConfig = { @@ -212,7 +213,7 @@ export default class MenuUiHandler extends MessageUiHandler { manageDataOptions.push({ label: i18next.t("menuUiHandler:exportSession"), handler: () => { - const dataSlots: integer[] = []; + const dataSlots: number[] = []; Promise.all( new Array(5).fill(null).map((_, i) => { const slotId = i; @@ -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, @@ -680,7 +686,7 @@ export default class MenuUiHandler extends MessageUiHandler { super.showText(text, delay, callback, callbackDelay, prompt, promptDelay); } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { const ret = super.setCursor(cursor); if (!this.cursorObj) { diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index 81a09e90167..18e1dfb1aff 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -34,24 +34,24 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { } } - showText(text: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) { + showText(text: string, delay?: number | null, callback?: Function | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null) { this.showTextInternal(text, delay, callback, callbackDelay, prompt, promptDelay); } - showDialogue(text: string, name?: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) { + showDialogue(text: string, name?: string, delay?: number | null, callback?: Function | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null) { this.showTextInternal(text, delay, callback, callbackDelay, prompt, promptDelay); } - private showTextInternal(text: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) { + private showTextInternal(text: string, delay?: number | null, callback?: Function | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null) { if (delay === null || delay === undefined) { delay = 20; } // Pattern matching regex that checks for @c{}, @f{}, @s{}, and @f{} patterns within message text and parses them to their respective behaviors. - const charVarMap = new Map(); - const delayMap = new Map(); - const soundMap = new Map(); - const fadeMap = new Map(); + const charVarMap = new Map(); + const delayMap = new Map(); + const soundMap = new Map(); + const fadeMap = new Map(); const actionPattern = /@(c|d|s|f)\{(.*?)\}/; let actionMatch: RegExpExecArray | null; while ((actionMatch = actionPattern.exec(text))) { @@ -188,7 +188,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { } } - showPrompt(callback?: Function | null, callbackDelay?: integer | null) { + showPrompt(callback?: Function | null, callbackDelay?: number | null) { const wrappedTextLines = this.message.runWordWrap(this.message.text).split(/\n/g); const textLinesCount = wrappedTextLines.length; const lastTextLine = wrappedTextLines[wrappedTextLines.length - 1]; diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 0cca087ce8d..a3508532631 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -35,15 +35,15 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { private moveInfoOverlay: MoveInfoOverlay; private moveInfoOverlayActive: boolean = false; - private rowCursor: integer = 0; + private rowCursor: number = 0; private player: boolean; /** * If reroll cost is negative, it is assumed there are 0 items in the shop. * It will cause reroll button to be disabled, and a "Continue" button to show in the place of shop items */ - private rerollCost: integer; - private transferButtonWidth: integer; - private checkButtonWidth: integer; + private rerollCost: number; + private transferButtonWidth: number; + private checkButtonWidth: number; public options: ModifierOption[]; public shopOptionsRows: ModifierOption[][]; @@ -185,7 +185,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.rerollButtonContainer.setPositionRelative(this.lockRarityButtonContainer, 0, canLockRarities ? -12 : 0); - this.rerollCost = args[3] as integer; + this.rerollCost = args[3] as number; this.updateRerollCostText(); @@ -460,7 +460,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { return success; } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { const ui = this.getUi(); const ret = super.setCursor(cursor); @@ -516,7 +516,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { return ret; } - setRowCursor(rowCursor: integer): boolean { + setRowCursor(rowCursor: number): boolean { const lastRowCursor = this.rowCursor; if (rowCursor !== lastRowCursor) { @@ -553,7 +553,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { return false; } - private getRowItems(rowCursor: integer): integer { + private getRowItems(rowCursor: number): number { switch (rowCursor) { case 0: return 3; @@ -564,7 +564,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } } - setRerollCost(rerollCost: integer): void { + setRerollCost(rerollCost: number): void { this.rerollCost = rerollCost; } @@ -730,7 +730,7 @@ class ModifierOption extends Phaser.GameObjects.Container { } } - show(remainingDuration: integer, upgradeCountOffset: integer) { + show(remainingDuration: number, upgradeCountOffset: number) { if (!this.modifierTypeOption.cost) { globalScene.tweens.add({ targets: this.pb, @@ -847,8 +847,8 @@ class ModifierOption extends Phaser.GameObjects.Container { }); } - getPbAtlasKey(tierOffset: integer = 0) { - return getPokeballAtlasKey((this.modifierTypeOption.type?.tier! + tierOffset) as integer as PokeballType); // TODO: is this bang correct? + getPbAtlasKey(tierOffset: number = 0) { + return getPokeballAtlasKey((this.modifierTypeOption.type?.tier! + tierOffset) as number as PokeballType); // TODO: is this bang correct? } updateCostText(): void { diff --git a/src/ui/party-exp-bar.ts b/src/ui/party-exp-bar.ts index 93e4117157a..75970a5908b 100644 --- a/src/ui/party-exp-bar.ts +++ b/src/ui/party-exp-bar.ts @@ -29,7 +29,7 @@ export default class PartyExpBar extends Phaser.GameObjects.Container { this.shown = false; } - showPokemonExp(pokemon: Pokemon, expValue: integer, showOnlyLevelUp: boolean, newLevel: number): Promise { + showPokemonExp(pokemon: Pokemon, expValue: number, showOnlyLevelUp: boolean, newLevel: number): Promise { return new Promise(resolve => { if (this.shown) { return resolve(); diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 4a7716f7e62..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, @@ -125,16 +126,16 @@ export enum PartyOption { ALL = 4000, } -export type PartySelectCallback = (cursor: integer, option: PartyOption) => void; -export type PartyModifierTransferSelectCallback = (fromCursor: integer, index: integer, itemQuantity?: integer, toCursor?: integer) => void; -export type PartyModifierSpliceSelectCallback = (fromCursor: integer, toCursor?: integer) => void; +export type PartySelectCallback = (cursor: number, option: PartyOption) => void; +export type PartyModifierTransferSelectCallback = (fromCursor: number, index: number, itemQuantity?: number, toCursor?: number) => void; +export type PartyModifierSpliceSelectCallback = (fromCursor: number, toCursor?: number) => void; export type PokemonSelectFilter = (pokemon: PlayerPokemon) => string | null; export type PokemonModifierTransferSelectFilter = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => string | null; export type PokemonMoveSelectFilter = (pokemonMove: PokemonMove) => string | null; export default class PartyUiHandler extends MessageUiHandler { private partyUiMode: PartyUiMode; - private fieldIndex: integer; + private fieldIndex: number; private partyBg: Phaser.GameObjects.Image; private partyContainer: Phaser.GameObjects.Container; @@ -146,26 +147,26 @@ export default class PartyUiHandler extends MessageUiHandler { private optionsMode: boolean; private optionsScroll: boolean; - private optionsCursor: integer = 0; - private optionsScrollCursor: integer = 0; - private optionsScrollTotal: integer = 0; + private optionsCursor: number = 0; + private optionsScrollCursor: number = 0; + private optionsScrollTotal: number = 0; /** This is only public for test/ui/transfer-item.test.ts */ public optionsContainer: Phaser.GameObjects.Container; private optionsBg: Phaser.GameObjects.NineSlice; private optionsCursorObj: Phaser.GameObjects.Image | null; - private options: integer[]; + private options: number[]; private transferMode: boolean; - private transferOptionCursor: integer; - private transferCursor: integer; + private transferOptionCursor: number; + private transferCursor: number; /** Current quantity selection for every item held by the pokemon selected for the transfer */ - private transferQuantities: integer[]; + private transferQuantities: number[]; /** Stack size of every item that the selected pokemon is holding */ - private transferQuantitiesMax: integer[]; + private transferQuantitiesMax: number[]; /** Whether to transfer all items */ private transferAll: boolean; - private lastCursor: integer = 0; + private lastCursor: number = 0; private selectCallback: PartySelectCallback | PartyModifierTransferSelectCallback | null; private selectFilter: PokemonSelectFilter | PokemonModifierTransferSelectFilter; private moveSelectFilter: PokemonMoveSelectFilter; @@ -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); @@ -304,7 +305,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.partyUiMode = args[0] as PartyUiMode; - this.fieldIndex = args.length > 1 ? args[1] as integer : -1; + this.fieldIndex = args.length > 1 ? args[1] as number : -1; this.selectCallback = args.length > 2 && args[2] instanceof Function ? args[2] : undefined; this.selectFilter = args.length > 3 && args[3] instanceof Function @@ -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(); @@ -689,7 +700,7 @@ export default class PartyUiHandler extends MessageUiHandler { } } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { let changed: boolean; if (this.optionsMode) { @@ -749,7 +760,7 @@ export default class PartyUiHandler extends MessageUiHandler { return changed; } - showText(text: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) { + showText(text: string, delay?: number | null, callback?: Function | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null) { if (text.length === 0) { text = defaultMessage; } @@ -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)))) { @@ -1060,7 +1072,7 @@ export default class PartyUiHandler extends MessageUiHandler { } } - doRelease(slotIndex: integer): void { + doRelease(slotIndex: number): void { this.showText(this.getReleaseMessage(getPokemonNameWithAffix(globalScene.getPlayerParty()[slotIndex])), null, () => { this.clearPartySlots(); globalScene.removePartyMemberModifiers(slotIndex); @@ -1121,7 +1133,7 @@ export default class PartyUiHandler extends MessageUiHandler { return formChangeItemModifiers; } - getOptionsCursorWithScroll(): integer { + getOptionsCursorWithScroll(): number { return this.optionsCursor + this.optionsScrollCursor + (this.options && this.options[0] === PartyOption.SCROLL_UP ? -1 : 0); } @@ -1164,7 +1176,7 @@ export default class PartyUiHandler extends MessageUiHandler { class PartySlot extends Phaser.GameObjects.Container { private selected: boolean; private transfer: boolean; - private slotIndex: integer; + private slotIndex: number; private pokemon: PlayerPokemon; private slotBg: Phaser.GameObjects.Image; @@ -1179,7 +1191,7 @@ class PartySlot extends Phaser.GameObjects.Container { private pokemonIcon: Phaser.GameObjects.Container; private iconAnimHandler: PokemonIconAnimHandler; - constructor(slotIndex: integer, pokemon: PlayerPokemon, iconAnimHandler: PokemonIconAnimHandler, partyUiMode: PartyUiMode, tmMoveId: Moves) { + constructor(slotIndex: number, pokemon: PlayerPokemon, iconAnimHandler: PokemonIconAnimHandler, partyUiMode: PartyUiMode, tmMoveId: Moves) { super(globalScene, slotIndex >= globalScene.currentBattle.getBattlerCount() ? 230.5 : 64, slotIndex >= globalScene.currentBattle.getBattlerCount() ? -184 + (globalScene.currentBattle.double ? -40 : 0) + (28 + (globalScene.currentBattle.double ? 8 : 0)) * slotIndex : -124 + (globalScene.currentBattle.double ? -8 : 0) + slotIndex * 64); 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/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 4f7a28f1d6d..554cea39731 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -17,8 +17,8 @@ import { Species } from "#enums/species"; interface LanguageSetting { infoContainerTextSize: string; - infoContainerLabelXPos?: integer; - infoContainerTextXPos?: integer; + infoContainerLabelXPos?: number; + infoContainerTextXPos?: number; } const languageSettings: { [key: string]: LanguageSetting } = { @@ -330,7 +330,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { } const starterSpeciesId = pokemon.species.getRootSpeciesId(); - const originalIvs: integer[] | null = eggInfo ? (dexEntry.caughtAttr ? dexEntry.ivs : null) : (globalScene.gameData.dexData[starterSpeciesId].caughtAttr + const originalIvs: number[] | null = eggInfo ? (dexEntry.caughtAttr ? dexEntry.ivs : null) : (globalScene.gameData.dexData[starterSpeciesId].caughtAttr ? globalScene.gameData.dexData[starterSpeciesId].ivs : null); this.statsContainer.updateIvs(pokemon.ivs, originalIvs!); // TODO: is this bang correct? diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 43b95cb3793..02bcba17a73 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -59,7 +59,7 @@ export default class RunInfoUiHandler extends UiHandler { private runResultContainer: Phaser.GameObjects.Container; private runInfoContainer: Phaser.GameObjects.Container; private partyContainer: Phaser.GameObjects.Container; - private statsBgWidth: integer; + private statsBgWidth: number; private hallofFameContainer: Phaser.GameObjects.Container; private endCardContainer: Phaser.GameObjects.Container; @@ -534,7 +534,7 @@ export default class RunInfoUiHandler extends UiHandler { // Luck // Uses the parameters windowX and windowY to dynamically position the luck value neatly into the bottom right corner const luckText = addBBCodeTextObject(0, 0, "", TextStyle.WINDOW, { fontSize: "55px" }); - const luckValue = Phaser.Math.Clamp(this.runInfo.party.map(p => p.toPokemon().getLuck()).reduce((total: integer, value: integer) => total += value, 0), 0, 14); + const luckValue = Phaser.Math.Clamp(this.runInfo.party.map(p => p.toPokemon().getLuck()).reduce((total: number, value: number) => total += value, 0), 0, 14); let luckInfo = i18next.t("runHistory:luck") + ": " + getLuckString(luckValue); if (luckValue < 14) { luckInfo = "[color=#" + (getLuckTextTint(luckValue)).toString(16) + "]" + luckInfo + "[/color]"; @@ -621,7 +621,7 @@ export default class RunInfoUiHandler extends UiHandler { const currentLanguage = i18next.resolvedLanguage ?? "en"; const windowHeight = ((globalScene.game.canvas.height / 6) - 23) / 6; - party.forEach((p: PokemonData, i: integer) => { + party.forEach((p: PokemonData, i: number) => { const pokemonInfoWindow = new RoundRectangle(globalScene, 0, 14, (this.statsBgWidth * 2) + 10, windowHeight - 2, 3); const pokemon = p.toPokemon(); diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index fe2ac9e1221..e746c9302d0 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -20,7 +20,7 @@ export enum SaveSlotUiMode { SAVE } -export type SaveSlotSelectCallback = (cursor: integer) => void; +export type SaveSlotSelectCallback = (cursor: number) => void; export default class SaveSlotSelectUiHandler extends MessageUiHandler { @@ -33,7 +33,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { private uiMode: SaveSlotUiMode; private saveSlotSelectCallback: SaveSlotSelectCallback | null; - private scrollCursor: integer = 0; + private scrollCursor: number = 0; private cursorObj: Phaser.GameObjects.Container | null; @@ -210,7 +210,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } } - showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) { + showText(text: string, delay?: number, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number) { super.showText(text, delay, callback, callbackDelay, prompt, promptDelay); if (text?.indexOf("\n") === -1) { @@ -230,7 +230,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { * @param prevSlotIndex index of the previous session occupied by the cursor, between `0` and `SESSION_SLOTS_COUNT - 1` - optional * @returns `true` if the cursor position has changed | `false` if it has not */ - override setCursor(cursor: integer, prevSlotIndex?: integer): boolean { + override setCursor(cursor: number, prevSlotIndex?: number): boolean { const changed = super.setCursor(cursor); if (!this.cursorObj) { @@ -267,7 +267,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { /** * Helper function that resets the given session slot to its default central position */ - revertSessionSlot(slotIndex: integer): void { + revertSessionSlot(slotIndex: number): void { const sessionSlot = this.sessionSlots[slotIndex]; if (sessionSlot) { sessionSlot.setPosition(0, slotIndex * 56); @@ -291,7 +291,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { * @param prevSlotIndex index of the previous slot occupied by the cursor, between `0` and `SESSION_SLOTS_COUNT-1` - optional * @returns `true` if the cursor position has changed | `false` if it has not */ - setScrollCursor(scrollCursor: integer, prevSlotIndex?: integer): boolean { + setScrollCursor(scrollCursor: number, prevSlotIndex?: number): boolean { const changed = scrollCursor !== this.scrollCursor; if (changed) { @@ -331,13 +331,13 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } class SessionSlot extends Phaser.GameObjects.Container { - public slotId: integer; + public slotId: number; public hasData: boolean; private loadingLabel: Phaser.GameObjects.Text; public saveData: SessionSaveData; - constructor(slotId: integer) { + constructor(slotId: number) { super(globalScene, 0, slotId * 56); this.slotId = slotId; @@ -367,7 +367,7 @@ class SessionSlot extends Phaser.GameObjects.Container { this.add(playTimeLabel); const pokemonIconsContainer = globalScene.add.container(144, 4); - data.party.forEach((p: PokemonData, i: integer) => { + data.party.forEach((p: PokemonData, i: number) => { const iconContainer = globalScene.add.container(26 * i, 0); iconContainer.setScale(0.75); diff --git a/src/ui/settings/abstract-binding-ui-handler.ts b/src/ui/settings/abstract-binding-ui-handler.ts index d0f9a5c8a90..e8c3e20c38f 100644 --- a/src/ui/settings/abstract-binding-ui-handler.ts +++ b/src/ui/settings/abstract-binding-ui-handler.ts @@ -203,7 +203,7 @@ export default abstract class AbstractBindingUiHandler extends UiHandler { * @param cursor - The cursor position to set. * @returns `true` if the cursor was set successfully. */ - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { this.cursor = cursor; if (cursor === 1) { this.actionLabel.setColor(this.getTextColor(TextStyle.SETTINGS_SELECTED)); diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index a0287f80f79..7300b6d3266 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -463,7 +463,7 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { this.cursorObj = null; } - override showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) { + override showText(text: string, delay?: number, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number) { this.messageBoxContainer.setVisible(!!text?.length); super.showText(text, delay, callback, callbackDelay, prompt, promptDelay); } diff --git a/src/ui/settings/option-select-ui-handler.ts b/src/ui/settings/option-select-ui-handler.ts index 5deaba5cfc5..b3d1735dc19 100644 --- a/src/ui/settings/option-select-ui-handler.ts +++ b/src/ui/settings/option-select-ui-handler.ts @@ -6,7 +6,7 @@ export default class OptionSelectUiHandler extends AbstractOptionSelectUiHandler super(mode); } - getWindowWidth(): integer { + getWindowWidth(): number { return 64; } } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index de7b182c5c1..20ca2cc88da 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -62,7 +62,7 @@ export type StarterSelectCallback = (starters: Starter[]) => void; export interface Starter { species: PokemonSpecies; dexAttr: bigint; - abilityIndex: integer, + abilityIndex: number, passive: boolean; nature: Nature; moveset?: StarterMoveset; @@ -73,8 +73,8 @@ export interface Starter { interface LanguageSetting { starterInfoTextSize: string, instructionTextSize: string, - starterInfoXPos?: integer, - starterInfoYOffset?: integer + starterInfoXPos?: number, + starterInfoYOffset?: number } const languageSettings: { [key: string]: LanguageSetting } = { @@ -206,11 +206,11 @@ function findClosestStarterRow(index: number, numberOfRows: number) { interface SpeciesDetails { shiny?: boolean, - formIndex?: integer + formIndex?: number female?: boolean, variant?: Variant, - abilityIndex?: integer, - natureIndex?: integer, + abilityIndex?: number, + natureIndex?: number, forSeen?: boolean, // default = false } @@ -292,7 +292,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private dexAttrCursor: bigint = 0n; private abilityCursor: number = -1; private natureCursor: number = -1; - private filterBarCursor: integer = 0; + private filterBarCursor: number = 0; private starterMoveset: StarterMoveset | null; private scrollCursor: number; @@ -302,7 +302,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { public starterSpecies: PokemonSpecies[] = []; private pokerusSpecies: PokemonSpecies[] = []; private starterAttr: bigint[] = []; - private starterAbilityIndexes: integer[] = []; + private starterAbilityIndexes: number[] = []; private starterNatures: Nature[] = []; private starterMovesets: StarterMoveset[] = []; private speciesStarterDexEntry: DexEntry | null; @@ -1072,7 +1072,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } - showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer, moveToTop?: boolean) { + 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; @@ -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: () => { @@ -2105,7 +2120,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const natureIndex = natures.indexOf(this.natureCursor); const newNature = natures[natureIndex < natures.length - 1 ? natureIndex + 1 : 0]; // store cycled nature as default - starterAttributes.nature = newNature as unknown as integer; + starterAttributes.nature = newNature as unknown as number; this.setSpeciesDetails(this.lastSpecies, { natureIndex: newNature }); success = true; } @@ -2274,7 +2289,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return [ isDupe, removeIndex ]; } - addToParty(species: PokemonSpecies, dexAttr: bigint, abilityIndex: integer, nature: Nature, moveset: StarterMoveset, randomSelection: boolean = false) { + addToParty(species: PokemonSpecies, dexAttr: bigint, abilityIndex: number, nature: Nature, moveset: StarterMoveset, randomSelection: boolean = false) { const props = globalScene.gameData.getSpeciesDexAttrProps(species, dexAttr); this.starterIcons[this.starterSpecies.length].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant)); this.starterIcons[this.starterSpecies.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant)); @@ -2783,7 +2798,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }); }; - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { let changed = false; if (this.filterMode) { @@ -3379,7 +3394,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ? speciesMoveData : speciesMoveData[formIndex!] // TODO: is this bang correct? : null; - const availableStarterMoves = this.speciesStarterMoves.concat(speciesEggMoves.hasOwnProperty(species.speciesId) ? speciesEggMoves[species.speciesId].filter((_, em: integer) => globalScene.gameData.starterData[species.speciesId].eggMoves & (1 << em)) : []); + const availableStarterMoves = this.speciesStarterMoves.concat(speciesEggMoves.hasOwnProperty(species.speciesId) ? speciesEggMoves[species.speciesId].filter((_, em: number) => globalScene.gameData.starterData[species.speciesId].eggMoves & (1 << em)) : []); this.starterMoveset = (moveData || (this.speciesStarterMoves.slice(0, 4) as StarterMoveset)).filter(m => availableStarterMoves.find(sm => sm === m)) as StarterMoveset; // Consolidate move data if it contains an incompatible move if (this.starterMoveset.length < 4 && this.starterMoveset.length < availableStarterMoves.length) { @@ -3548,8 +3563,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } - tryUpdateValue(add?: integer, addingToParty?: boolean): boolean { - const value = this.starterSpecies.map(s => s.generation).reduce((total: integer, _gen: integer, i: integer) => total += globalScene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); + tryUpdateValue(add?: number, addingToParty?: boolean): boolean { + const value = this.starterSpecies.map(s => s.generation).reduce((total: number, _gen: number, i: number) => total += globalScene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); const newValue = value + (add || 0); const valueLimit = this.getValueLimit(); const overLimit = newValue > valueLimit; diff --git a/src/ui/stats-container.ts b/src/ui/stats-container.ts index 8d8d3c101b2..add7eeedbb8 100644 --- a/src/ui/stats-container.ts +++ b/src/ui/stats-container.ts @@ -17,7 +17,7 @@ const defaultIvChartData = new Array(12).fill(null).map(() => 0); export class StatsContainer extends Phaser.GameObjects.Container { private showDiff: boolean; - private statsIvsCache: integer[]; + private statsIvsCache: number[]; private ivChart: Phaser.GameObjects.Polygon; private ivStatValueTexts: BBCodeText[]; @@ -31,7 +31,7 @@ export class StatsContainer extends Phaser.GameObjects.Container { setup() { this.setName("stats"); - const ivChartBgData = new Array(6).fill(null).map((_, i: integer) => [ ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][0], ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][1] ] ).flat(); + const ivChartBgData = new Array(6).fill(null).map((_, i: number) => [ ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][0], ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][1] ] ).flat(); const ivChartBg = globalScene.add.polygon(48, 44, ivChartBgData, 0xd8e0f0, 0.625); ivChartBg.setOrigin(0, 0); @@ -74,14 +74,14 @@ export class StatsContainer extends Phaser.GameObjects.Container { } } - updateIvs(ivs: integer[], originalIvs?: integer[]): void { + updateIvs(ivs: number[], originalIvs?: number[]): void { if (ivs) { const ivChartData = new Array(6).fill(null).map((_, i) => [ (ivs[ivChartStatIndexes[i]] / 31) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][0], (ivs[ivChartStatIndexes[i]] / 31) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][1] ] ).flat(); const lastIvChartData = this.statsIvsCache || defaultIvChartData; const perfectIVColor: string = getTextColor(TextStyle.SUMMARY_GOLD, false, globalScene.uiTheme); this.statsIvsCache = ivChartData.slice(0); - this.ivStatValueTexts.map((t: BBCodeText, i: integer) => { + this.ivStatValueTexts.map((t: BBCodeText, i: number) => { let label = ""; // Check to see if IVs are 31, if so change the text style to gold, otherwise leave them be. @@ -114,7 +114,7 @@ export class StatsContainer extends Phaser.GameObjects.Container { ease: "Cubic.easeOut", onUpdate: (tween: Phaser.Tweens.Tween) => { const progress = tween.getValue(); - const interpolatedData = ivChartData.map((v: number, i: integer) => v * progress + (lastIvChartData[i] * (1 - progress))); + const interpolatedData = ivChartData.map((v: number, i: number) => v * progress + (lastIvChartData[i] * (1 - progress))); if (interpolateColor) { this.ivChart.setFillStyle( Phaser.Display.Color.ValueToColor( diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 3fe6a372737..cf5d40bc006 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -96,6 +96,9 @@ export default class SummaryUiHandler extends UiHandler { private friendshipText: Phaser.GameObjects.Text; private friendshipIcon: Phaser.GameObjects.Sprite; private friendshipOverlay: Phaser.GameObjects.Sprite; + private permStatsContainer: Phaser.GameObjects.Container; + private ivContainer: Phaser.GameObjects.Container; + private statsContainer: Phaser.GameObjects.Container; private descriptionScrollTween: Phaser.Tweens.Tween | null; private moveCursorBlinkTimer: Phaser.Time.TimerEvent | null; @@ -110,8 +113,8 @@ export default class SummaryUiHandler extends UiHandler { private moveEffectsVisible: boolean; private moveSelect: boolean; - private moveCursor: integer; - private selectedMoveIndex: integer; + private moveCursor: number; + private selectedMoveIndex: number; private selectCallback: Function | null; constructor() { @@ -289,7 +292,7 @@ export default class SummaryUiHandler extends UiHandler { this.summaryPageTransitionContainer.setVisible(false); } - getPageKey(page?: integer) { + getPageKey(page?: number) { if (page === undefined) { page = this.cursor; } @@ -534,6 +537,10 @@ export default class SummaryUiHandler extends UiHandler { this.passiveContainer.nameText?.setVisible(!this.passiveContainer.descriptionText?.visible); this.passiveContainer.descriptionText?.setVisible(!this.passiveContainer.descriptionText.visible); this.passiveContainer.labelImage.setVisible(!this.passiveContainer.labelImage.visible); + } else if (this.cursor === Page.STATS) { + //Show IVs + this.permStatsContainer.setVisible(!this.permStatsContainer.visible); + this.ivContainer.setVisible(!this.ivContainer.visible); } } else if (button === Button.CANCEL) { if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { @@ -597,7 +604,7 @@ export default class SummaryUiHandler extends UiHandler { return success || error; } - setCursor(cursor: integer, overrideChanged: boolean = false): boolean { + setCursor(cursor: number, overrideChanged: boolean = false): boolean { let changed: boolean = overrideChanged || this.moveCursor !== cursor; if (this.moveSelect) { @@ -756,7 +763,7 @@ export default class SummaryUiHandler extends UiHandler { typeLabel.setOrigin(0, 0); profileContainer.add(typeLabel); - const getTypeIcon = (index: integer, type: Type, tera: boolean = false) => { + const getTypeIcon = (index: number, type: Type, tera: boolean = false) => { const xCoord = typeLabel.width * typeLabel.scale + 9 + 34 * index; const typeIcon = !tera ? globalScene.add.sprite(xCoord, 42, Utils.getLocalizedSpriteKey("types"), Type[type].toLowerCase()) @@ -877,8 +884,13 @@ export default class SummaryUiHandler extends UiHandler { profileContainer.add(memoText); break; case Page.STATS: - const statsContainer = globalScene.add.container(0, -pageBg.height); - pageContainer.add(statsContainer); + this.statsContainer = globalScene.add.container(0, -pageBg.height); + pageContainer.add(this.statsContainer); + this.permStatsContainer = globalScene.add.container(27, 56); + this.statsContainer.add(this.permStatsContainer); + this.ivContainer = globalScene.add.container(27, 56); + this.statsContainer.add(this.ivContainer); + this.statsContainer.setVisible(true); PERMANENT_STATS.forEach((stat, s) => { const statName = i18next.t(getStatKey(stat)); @@ -887,18 +899,27 @@ export default class SummaryUiHandler extends UiHandler { const natureStatMultiplier = getNatureStatMultiplier(this.pokemon?.getNature()!, s); // TODO: is this bang correct? - const statLabel = addTextObject(27 + 115 * colIndex + (colIndex === 1 ? 5 : 0), 56 + 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE); + const statLabel = addTextObject(115 * colIndex + (colIndex === 1 ? 5 : 0), 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE); + const ivLabel = addTextObject(115 * colIndex + (colIndex === 1 ? 5 : 0), 16 * rowIndex, statName, this.pokemon?.ivs[stat] === 31 ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY); + statLabel.setOrigin(0.5, 0); - statsContainer.add(statLabel); + ivLabel.setOrigin(0.5, 0); + this.permStatsContainer.add(statLabel); + this.ivContainer.add(ivLabel); const statValueText = stat !== Stat.HP ? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct? : `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct? + const ivText = `${this.pokemon?.ivs[stat]}/31`; - const statValue = addTextObject(120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); + const statValue = addTextObject(93 + 88 * colIndex, 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); statValue.setOrigin(1, 0); - statsContainer.add(statValue); + this.permStatsContainer.add(statValue); + const ivValue = addTextObject(93 + 88 * colIndex, 16 * rowIndex, ivText, TextStyle.WINDOW_ALT); + ivValue.setOrigin(1, 0); + this.ivContainer.add(ivValue); }); + this.ivContainer.setVisible(false); const itemModifiers = (globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.pokemon?.id, this.playerParty) as PokemonHeldItemModifier[]) @@ -908,7 +929,7 @@ export default class SummaryUiHandler extends UiHandler { const icon = item.getIcon(true); icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); - statsContainer.add(icon); + this.statsContainer.add(icon); icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); icon.on("pointerover", () => globalScene.ui.showTooltip(item.type.name, item.type.getDescription(), true)); @@ -924,26 +945,26 @@ export default class SummaryUiHandler extends UiHandler { const expLabel = addTextObject(6, 112, i18next.t("pokemonSummary:expPoints"), TextStyle.SUMMARY); expLabel.setOrigin(0, 0); - statsContainer.add(expLabel); + this.statsContainer.add(expLabel); const nextLvExpLabel = addTextObject(6, 128, i18next.t("pokemonSummary:nextLv"), TextStyle.SUMMARY); nextLvExpLabel.setOrigin(0, 0); - statsContainer.add(nextLvExpLabel); + this.statsContainer.add(nextLvExpLabel); const expText = addTextObject(208, 112, pkmExp.toString(), TextStyle.WINDOW_ALT); expText.setOrigin(1, 0); - statsContainer.add(expText); + this.statsContainer.add(expText); const nextLvExp = pkmLvl < globalScene.getMaxExpLevel() ? getLevelTotalExp(pkmLvl + 1, pkmSpeciesGrowthRate) - pkmExp : 0; const nextLvExpText = addTextObject(208, 128, nextLvExp.toString(), TextStyle.WINDOW_ALT); nextLvExpText.setOrigin(1, 0); - statsContainer.add(nextLvExpText); + this.statsContainer.add(nextLvExpText); const expOverlay = globalScene.add.image(140, 145, "summary_stats_overlay_exp"); expOverlay.setOrigin(0, 0); - statsContainer.add(expOverlay); + this.statsContainer.add(expOverlay); const expMaskRect = globalScene.make.graphics({}); expMaskRect.setScale(6); @@ -954,6 +975,11 @@ export default class SummaryUiHandler extends UiHandler { const expMask = expMaskRect.createGeometryMask(); expOverlay.setMask(expMask); + this.abilityPrompt = globalScene.add.image(0, 0, !globalScene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a"); + this.abilityPrompt.setPosition(8, 47); + this.abilityPrompt.setVisible(true); + this.abilityPrompt.setOrigin(0, 0); + this.statsContainer.add(this.abilityPrompt); break; case Page.MOVES: this.movesContainer = globalScene.add.container(5, -pageBg.height + 26); diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index 232ec887b0a..681c5ff40c0 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -41,7 +41,7 @@ export default class TargetSelectUiHandler extends UiHandler { super.show(args); - this.fieldIndex = args[0] as integer; + this.fieldIndex = args[0] as number; this.move = args[1] as Moves; this.targetSelectCallback = args[2] as TargetSelectCallback; const user = globalScene.getPlayerField()[this.fieldIndex]; @@ -131,7 +131,7 @@ export default class TargetSelectUiHandler extends UiHandler { return success; } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { const singleTarget = globalScene.getField()[cursor]; const multipleTargets = this.targets.map(index => globalScene.getField()[index]); diff --git a/src/ui/text.ts b/src/ui/text.ts index cdd1142e7ee..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,10 +360,16 @@ 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"; } } -export function getModifierTierTextTint(tier: ModifierTier): integer { +export function getModifierTierTextTint(tier: ModifierTier): number { switch (tier) { case ModifierTier.COMMON: return 0xf8f8f8; @@ -379,7 +386,7 @@ export function getModifierTierTextTint(tier: ModifierTier): integer { } } -export function getEggTierTextTint(tier: EggTier): integer { +export function getEggTierTextTint(tier: EggTier): number { switch (tier) { case EggTier.COMMON: return getModifierTierTextTint(ModifierTier.COMMON); 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-handler.ts b/src/ui/ui-handler.ts index 89f8d9e65b6..f001ab5bd6f 100644 --- a/src/ui/ui-handler.ts +++ b/src/ui/ui-handler.ts @@ -8,8 +8,8 @@ import type { Button } from "#enums/buttons"; * A basic abstract class to act as a holder and processor for UI elements. */ export default abstract class UiHandler { - protected mode: integer | null; - protected cursor: integer = 0; + protected mode: number | null; + protected cursor: number = 0; public active: boolean = false; /** @@ -37,11 +37,11 @@ export default abstract class UiHandler { return getTextColor(style, shadow, globalScene.uiTheme); } - getCursor(): integer { + getCursor(): number { return this.cursor; } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { const changed = this.cursor !== cursor; if (changed) { this.cursor = cursor; diff --git a/src/ui/ui-theme.ts b/src/ui/ui-theme.ts index 0d42815f842..36e2e844a56 100644 --- a/src/ui/ui-theme.ts +++ b/src/ui/ui-theme.ts @@ -70,7 +70,7 @@ export function addWindow(x: number, y: number, width: number, height: number, m return window; } -export function updateWindowType(windowTypeIndex: integer): void { +export function updateWindowType(windowTypeIndex: number): void { const windowObjects: [Phaser.GameObjects.NineSlice, WindowVariant][] = []; const themedObjects: (Phaser.GameObjects.Image | Phaser.GameObjects.NineSlice)[] = []; const traverse = (object: any) => { diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 682ef836765..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(), @@ -296,13 +308,13 @@ export default class UI extends Phaser.GameObjects.Container { return handler.processInput(button); } - showTextPromise(text: string, callbackDelay: number = 0, prompt: boolean = true, promptDelay?: integer | null): Promise { + showTextPromise(text: string, callbackDelay: number = 0, prompt: boolean = true, promptDelay?: number | null): Promise { return new Promise(resolve => { this.showText(text ?? "", null, () => resolve(), callbackDelay, prompt, promptDelay); }); } - showText(text: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null): void { + showText(text: string, delay?: number | null, callback?: Function | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null): void { if (prompt && text.indexOf("$") > -1) { const messagePages = text.split(/\$/g).map(m => m.trim()); let showMessageAndCallback = () => callback && callback(); @@ -322,7 +334,7 @@ export default class UI extends Phaser.GameObjects.Container { } } - showDialogue(keyOrText: string, name: string | undefined, delay: integer | null = 0, callback: Function, callbackDelay?: integer, promptDelay?: integer): void { + showDialogue(keyOrText: string, name: string | undefined, delay: number | null = 0, callback: Function, callbackDelay?: number, promptDelay?: number): void { // Get localized dialogue (if available) let hasi18n = false; let text = keyOrText; @@ -443,7 +455,7 @@ export default class UI extends Phaser.GameObjects.Container { } } - setCursor(cursor: integer): boolean { + setCursor(cursor: number): boolean { const changed = this.getHandler().setCursor(cursor); if (changed) { this.playSelect(); @@ -460,7 +472,7 @@ export default class UI extends Phaser.GameObjects.Container { globalScene.playSound("ui/error"); } - fadeOut(duration: integer): Promise { + fadeOut(duration: number): Promise { return new Promise(resolve => { if (this.overlayActive) { return resolve(); @@ -478,7 +490,7 @@ export default class UI extends Phaser.GameObjects.Container { }); } - fadeIn(duration: integer): Promise { + fadeIn(duration: number): Promise { return new Promise(resolve => { if (!this.overlayActive) { return resolve(); @@ -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); } diff --git a/src/utils.ts b/src/utils.ts index 2235fb69633..a906ee76391 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -11,7 +11,7 @@ export function toReadableString(str: string): string { return str.replace(/\_/g, " ").split(" ").map(s => `${s.slice(0, 1)}${s.slice(1).toLowerCase()}`).join(" "); } -export function randomString(length: integer, seeded: boolean = false) { +export function randomString(length: number, seeded: boolean = false) { const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; @@ -23,7 +23,7 @@ export function randomString(length: integer, seeded: boolean = false) { return result; } -export function shiftCharCodes(str: string, shiftCount: integer) { +export function shiftCharCodes(str: string, shiftCount: number) { if (!shiftCount) { shiftCount = 0; } @@ -59,7 +59,7 @@ export function randSeedGauss(stdev: number, mean: number = 0): number { return z * stdev + mean; } -export function padInt(value: integer, length: integer, padWith?: string): string { +export function padInt(value: number, length: number, padWith?: string): string { if (!padWith) { padWith = "0"; } @@ -75,7 +75,7 @@ export function padInt(value: integer, length: integer, padWith?: string): strin * @param range The amount of possible numbers * @param min The starting number */ -export function randInt(range: integer, min: integer = 0): integer { +export function randInt(range: number, min: number = 0): number { if (range === 1) { return min; } @@ -88,7 +88,7 @@ export function randInt(range: integer, min: integer = 0): integer { * @param min The minimum integer to pick, default `0` * @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) */ -export function randSeedInt(range: integer, min: integer = 0): integer { +export function randSeedInt(range: number, min: number = 0): number { if (range <= 1) { return min; } @@ -100,7 +100,7 @@ export function randSeedInt(range: integer, min: integer = 0): integer { * @param min The lowest number * @param max The highest number */ -export function randIntRange(min: integer, max: integer): integer { +export function randIntRange(min: number, max: number): number { return randInt(max - min, min); } @@ -139,7 +139,7 @@ export function randSeedShuffle(items: T[]): T[] { return newArray; } -export function getFrameMs(frameCount: integer): integer { +export function getFrameMs(frameCount: number): number { return Math.floor((1 / 60) * 1000 * frameCount); } @@ -150,7 +150,7 @@ export function getCurrentTime(): number { const secondsInHour = 3600; -export function getPlayTimeString(totalSeconds: integer): string { +export function getPlayTimeString(totalSeconds: number): string { const days = `${Math.floor(totalSeconds / (secondsInHour * 24))}`; const hours = `${Math.floor(totalSeconds % (secondsInHour * 24) / secondsInHour)}`; const minutes = `${Math.floor(totalSeconds % secondsInHour / 60)}`; @@ -176,7 +176,7 @@ export function getIvsFromId(id: number): number[] { ]; } -export function formatLargeNumber(count: integer, threshold: integer): string { +export function formatLargeNumber(count: number, threshold: number): string { if (count < threshold) { return count.toString(); } @@ -236,7 +236,7 @@ export function formatMoney(format: MoneyFormat, amount: number) { return amount.toLocaleString(); } -export function formatStat(stat: integer, forHp: boolean = false): string { +export function formatStat(stat: number, forHp: boolean = false): string { return formatLargeNumber(stat, forHp ? 100000 : 1000000); } @@ -244,7 +244,7 @@ export function getEnumKeys(enumType: any): string[] { return Object.values(enumType).filter(v => isNaN(parseInt(v!.toString()))).map(v => v!.toString()); } -export function getEnumValues(enumType: any): integer[] { +export function getEnumValues(enumType: any): number[] { return Object.values(enumType).filter(v => !isNaN(parseInt(v!.toString()))).map(v => parseInt(v!.toString())); } @@ -344,7 +344,7 @@ export class NumberHolder { /** @deprecated Use {@linkcode NumberHolder} */ export class IntegerHolder extends NumberHolder { - constructor(value: integer) { + constructor(value: number) { super(value); } } @@ -357,8 +357,8 @@ export class FixedInt { } } -export function fixedInt(value: integer): integer { - return new FixedInt(value) as unknown as integer; +export function fixedInt(value: number): number { + return new FixedInt(value) as unknown as number; } /** @@ -382,7 +382,7 @@ export function toCamelCaseString(unformattedText: string): string { return unformattedText.split(/[_ ]/).filter(f => f).map((f, i) => i ? `${f[0].toUpperCase()}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); } -export function rgbToHsv(r: integer, g: integer, b: integer) { +export function rgbToHsv(r: number, g: number, b: number) { const v = Math.max(r, g, b); const c = v - Math.min(r, g, b); const h = c && ((v === r) ? (g - b) / c : ((v === g) ? 2 + (b - r) / c : 4 + (r - g) / c)); @@ -394,7 +394,7 @@ export function rgbToHsv(r: integer, g: integer, b: integer) { * @param {Array} rgb1 First RGB color in array * @param {Array} rgb2 Second RGB color in array */ -export function deltaRgb(rgb1: integer[], rgb2: integer[]): integer { +export function deltaRgb(rgb1: number[], rgb2: number[]): number { const [ r1, g1, b1 ] = rgb1; const [ r2, g2, b2 ] = rgb2; const drp2 = Math.pow(r1 - r2, 2); @@ -415,7 +415,7 @@ export function rgbHexToRgba(hex: string) { }; } -export function rgbaToInt(rgba: integer[]): integer { +export function rgbaToInt(rgba: number[]): number { return (rgba[0] << 24) + (rgba[1] << 16) + (rgba[2] << 8) + rgba[3]; } diff --git a/tsconfig.json b/tsconfig.json index f8e019a1b8b..0ec945df4ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,6 @@ "paths": { "#enums/*": ["./enums/*.ts"], "#app/*": ["*.ts"], - "#app": ["."], "#test/*": ["./test/*.ts"] }, "outDir": "./build",