diff --git a/README.md b/README.md index f3ec1c793bd..b26fd56a7b1 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to - Pokémon Legends: Arceus - Pokémon Scarlet/Violet - Firel (Custom Ice Cave, Laboratory, Metropolis, Plains, Power Plant, Seabed, Space, and Volcano biome music) - - Lmz (Custom Jungle biome music) + - Lmz (Custom Ancient Ruins, Jungle, and Lake biome music) - Andr06 (Custom Slum and Sea biome music) ### 🎵 Sound Effects diff --git a/create-test-boilerplate.js b/create-test-boilerplate.js index d9cdbd4e7cf..6d9cde966d5 100644 --- a/create-test-boilerplate.js +++ b/create-test-boilerplate.js @@ -1,7 +1,3 @@ -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - /** * This script creates a test boilerplate file for a move or ability. * @param {string} type - The type of test to create. Either "move", "ability", @@ -10,63 +6,108 @@ import { fileURLToPath } from 'url'; * @example npm run create-test move tackle */ +import fs from "fs"; +import inquirer from "inquirer"; +import path from "path"; +import { fileURLToPath } from "url"; + // Get the directory name of the current module file const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +const typeChoices = ["Move", "Ability", "Item", "Mystery Encounter"]; -// Get the arguments from the command line -const args = process.argv.slice(2); -const type = args[0]; // "move" or "ability" -let fileName = args[1]; // The file name +/** + * Prompts the user to select a type via list. + * @returns {Promise<{selectedOption: string}>} the selected type + */ +async function promptTestType() { + const typeAnswer = await inquirer.prompt([ + { + type: "list", + name: "selectedOption", + message: "What type of test would you like to create:", + choices: [...typeChoices, "EXIT"], + }, + ]); -if (!type || !fileName) { - console.error('Please provide a type ("move", "ability", or "item") and a file name.'); - process.exit(1); + if (typeAnswer.selectedOption === "EXIT") { + console.log("Exiting..."); + return process.exit(); + } else if (!typeChoices.includes(typeAnswer.selectedOption)) { + console.error('Please provide a valid type ("move", "ability", or "item")!'); + return await promptTestType(); + } + + return typeAnswer; } -// Convert fileName from kebab-case or camelCase to snake_case -fileName = fileName - .replace(/-+/g, '_') // Convert kebab-case (dashes) to underscores - .replace(/([a-z])([A-Z])/g, '$1_$2') // Convert camelCase to snake_case - .toLowerCase(); // Ensure all lowercase +/** + * Prompts the user to provide a file name. + * @param {string} selectedType + * @returns {Promise<{userInput: string}>} the selected file name + */ +async function promptFileName(selectedType) { + const fileNameAnswer = await inquirer.prompt([ + { + type: "input", + name: "userInput", + message: `Please provide a file name for the ${selectedType} test:`, + }, + ]); -// Format the description for the test case -const formattedName = fileName - .replace(/_/g, ' ') - .replace(/\b\w/g, char => char.toUpperCase()); + if (!fileNameAnswer.userInput || fileNameAnswer.userInput.trim().length === 0) { + console.error("Please provide a valid file name!"); + return await promptFileName(selectedType); + } -// Determine the directory based on the type -let dir; -let description; -if (type === 'move') { - dir = path.join(__dirname, 'src', 'test', 'moves'); - description = `Moves - ${formattedName}`; -} else if (type === 'ability') { - dir = path.join(__dirname, 'src', 'test', 'abilities'); - description = `Abilities - ${formattedName}`; -} else if (type === "item") { - dir = path.join(__dirname, 'src', 'test', 'items'); - description = `Items - ${formattedName}`; -} else { - console.error('Invalid type. Please use "move", "ability", or "item".'); - process.exit(1); + return fileNameAnswer; } -// Ensure the directory exists -if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); -} +/** + * Runs the interactive create-test "CLI" + * @returns {Promise} + */ +async function runInteractive() { + const typeAnswer = await promptTestType(); + const fileNameAnswer = await promptFileName(typeAnswer.selectedOption); -// Create the file with the given name -const filePath = path.join(dir, `${fileName}.test.ts`); + const type = typeAnswer.selectedOption.toLowerCase(); + // Convert fileName from kebab-case or camelCase to snake_case + const fileName = fileNameAnswer.userInput + .replace(/-+/g, "_") // Convert kebab-case (dashes) to underscores + .replace(/([a-z])([A-Z])/g, "$1_$2") // Convert camelCase to snake_case + .replace(/\s+/g, '_') // Replace spaces with underscores + .toLowerCase(); // Ensure all lowercase + // Format the description for the test case -if (fs.existsSync(filePath)) { - console.error(`File "${fileName}.test.ts" already exists.`); - process.exit(1); -} + const formattedName = fileName.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase()); + // Determine the directory based on the type + let dir; + let description; + switch (type) { + case "move": + dir = path.join(__dirname, "src", "test", "moves"); + description = `Moves - ${formattedName}`; + break; + case "ability": + dir = path.join(__dirname, "src", "test", "abilities"); + description = `Abilities - ${formattedName}`; + break; + case "item": + dir = path.join(__dirname, "src", "test", "items"); + description = `Items - ${formattedName}`; + break; + case "mystery encounter": + dir = path.join(__dirname, "src", "test", "mystery-encounter", "encounters"); + description = `Mystery Encounter - ${formattedName}`; + break; + default: + console.error('Invalid type. Please use "move", "ability", or "item".'); + process.exit(1); + } -// Define the content template -const content = `import { Abilities } from "#enums/abilities"; + // Define the content template + const content = `import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; @@ -76,7 +117,6 @@ import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest"; describe("${description}", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -100,11 +140,27 @@ describe("${description}", () => { it("test case", async () => { // await game.classicMode.startBattle([Species.MAGIKARP]); // game.move.select(Moves.SPLASH); - }, TIMEOUT); + }); }); `; -// Write the template content to the file -fs.writeFileSync(filePath, content, 'utf8'); + // Ensure the directory exists + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } -console.log(`File created at: ${filePath}`); + // Create the file with the given name + const filePath = path.join(dir, `${fileName}.test.ts`); + + if (fs.existsSync(filePath)) { + console.error(`File "${fileName}.test.ts" already exists.`); + process.exit(1); + } + + // Write the template content to the file + fs.writeFileSync(filePath, content, "utf8"); + + console.log(`File created at: ${filePath}`); +} + +runInteractive(); diff --git a/package-lock.json b/package-lock.json index 4a447554819..344100e4f6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "dependency-cruiser": "^16.3.10", "eslint": "^9.7.0", "eslint-plugin-import-x": "^4.2.1", + "inquirer": "^11.0.2", "jsdom": "^24.0.0", "lefthook": "^1.6.12", "phaser3spectorjs": "^0.0.8", @@ -1070,6 +1071,281 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@inquirer/checkbox": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz", + "integrity": "sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-4.0.1.tgz", + "integrity": "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/@types/node": { + "version": "22.5.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", + "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@inquirer/core/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inquirer/core/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/editor": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-3.0.1.tgz", + "integrity": "sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-3.0.1.tgz", + "integrity": "sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.6.tgz", + "integrity": "sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-3.0.1.tgz", + "integrity": "sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-2.0.1.tgz", + "integrity": "sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-3.0.1.tgz", + "integrity": "sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-6.0.1.tgz", + "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^3.0.1", + "@inquirer/confirm": "^4.0.1", + "@inquirer/editor": "^3.0.1", + "@inquirer/expand": "^3.0.1", + "@inquirer/input": "^3.0.1", + "@inquirer/number": "^2.0.1", + "@inquirer/password": "^3.0.1", + "@inquirer/rawlist": "^3.0.1", + "@inquirer/search": "^2.0.1", + "@inquirer/select": "^3.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-3.0.1.tgz", + "integrity": "sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-2.0.1.tgz", + "integrity": "sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-3.0.1.tgz", + "integrity": "sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "dev": true, + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1563,6 +1839,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "20.14.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", @@ -1585,6 +1871,13 @@ "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", "dev": true }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.0.0-alpha.58", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.0-alpha.58.tgz", @@ -1970,6 +2263,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2192,6 +2501,13 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true, + "license": "MIT" + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -2202,6 +2518,16 @@ "node": ">= 16" } }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3057,6 +3383,21 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3579,6 +3920,19 @@ "i18next": ">=8.4.0" } }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -3626,6 +3980,26 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/inquirer": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-11.0.2.tgz", + "integrity": "sha512-pnbn3nL+JFrTw/pLhzyE/IQ3+gA3n5JxTAZQDjB6qu4gbjOaiTnpZbxT6HY2DDCT7bzDjTTsd3snRP+B6N//Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/prompts": "^6.0.1", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "ansi-escapes": "^4.3.2", + "mute-stream": "^1.0.0", + "run-async": "^3.0.0", + "rxjs": "^7.8.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -4456,6 +4830,16 @@ "mustache": "bin/mustache" } }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -4605,6 +4989,16 @@ "node": ">= 0.8.0" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5092,6 +5486,16 @@ "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", "dev": true }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5116,6 +5520,16 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", @@ -5538,6 +5952,19 @@ "node": ">=14.0.0" } }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "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", @@ -5673,6 +6100,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typedoc": { "version": "0.26.5", "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.5.tgz", @@ -6346,6 +6786,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index dddf5aedebd..2109604c969 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "dependency-cruiser": "^16.3.10", "eslint": "^9.7.0", "eslint-plugin-import-x": "^4.2.1", + "inquirer": "^11.0.2", "jsdom": "^24.0.0", "lefthook": "^1.6.12", "phaser3spectorjs": "^0.0.8", diff --git a/public/audio/bgm/battle_star_admin.mp3 b/public/audio/bgm/battle_star_admin.mp3 new file mode 100644 index 00000000000..461a9a2b262 Binary files /dev/null and b/public/audio/bgm/battle_star_admin.mp3 differ diff --git a/public/audio/bgm/battle_star_boss.mp3 b/public/audio/bgm/battle_star_boss.mp3 new file mode 100644 index 00000000000..51cb33139c6 Binary files /dev/null and b/public/audio/bgm/battle_star_boss.mp3 differ diff --git a/public/audio/bgm/battle_star_grunt.mp3 b/public/audio/bgm/battle_star_grunt.mp3 new file mode 100644 index 00000000000..13da4900eed Binary files /dev/null and b/public/audio/bgm/battle_star_grunt.mp3 differ diff --git a/public/audio/bgm/jungle.mp3 b/public/audio/bgm/jungle.mp3 index 3a21c9bdb41..fbb770b0ad3 100644 Binary files a/public/audio/bgm/jungle.mp3 and b/public/audio/bgm/jungle.mp3 differ diff --git a/public/audio/bgm/laboratory.mp3 b/public/audio/bgm/laboratory.mp3 index e2b617e590a..1eba66ef56f 100644 Binary files a/public/audio/bgm/laboratory.mp3 and b/public/audio/bgm/laboratory.mp3 differ diff --git a/public/audio/bgm/lake.mp3 b/public/audio/bgm/lake.mp3 index c61fef15e42..e6762935770 100644 Binary files a/public/audio/bgm/lake.mp3 and b/public/audio/bgm/lake.mp3 differ diff --git a/public/audio/bgm/metropolis.mp3 b/public/audio/bgm/metropolis.mp3 index 98c2eb396b6..514c9ae15b1 100644 Binary files a/public/audio/bgm/metropolis.mp3 and b/public/audio/bgm/metropolis.mp3 differ diff --git a/public/audio/bgm/mystery_encounter_delibirdy.mp3 b/public/audio/bgm/mystery_encounter_delibirdy.mp3 new file mode 100644 index 00000000000..515a429aaba Binary files /dev/null and b/public/audio/bgm/mystery_encounter_delibirdy.mp3 differ diff --git a/public/audio/bgm/mystery_encounter_fun_and_games.mp3 b/public/audio/bgm/mystery_encounter_fun_and_games.mp3 index a9660d75e90..7864bf7a73d 100644 Binary files a/public/audio/bgm/mystery_encounter_fun_and_games.mp3 and b/public/audio/bgm/mystery_encounter_fun_and_games.mp3 differ diff --git a/public/audio/bgm/mystery_encounter_gen_5_gts.mp3 b/public/audio/bgm/mystery_encounter_gen_5_gts.mp3 index 989a7f9c598..d03c8f8d4d5 100644 Binary files a/public/audio/bgm/mystery_encounter_gen_5_gts.mp3 and b/public/audio/bgm/mystery_encounter_gen_5_gts.mp3 differ diff --git a/public/audio/bgm/mystery_encounter_gen_6_gts.mp3 b/public/audio/bgm/mystery_encounter_gen_6_gts.mp3 index 2c574da66ae..c921a01c204 100644 Binary files a/public/audio/bgm/mystery_encounter_gen_6_gts.mp3 and b/public/audio/bgm/mystery_encounter_gen_6_gts.mp3 differ diff --git a/public/audio/bgm/mystery_encounter_weird_dream.mp3 b/public/audio/bgm/mystery_encounter_weird_dream.mp3 index a630fe549db..433e07bab08 100644 Binary files a/public/audio/bgm/mystery_encounter_weird_dream.mp3 and b/public/audio/bgm/mystery_encounter_weird_dream.mp3 differ diff --git a/public/audio/bgm/ruins.mp3 b/public/audio/bgm/ruins.mp3 index 62f31893423..3692c71562f 100644 Binary files a/public/audio/bgm/ruins.mp3 and b/public/audio/bgm/ruins.mp3 differ diff --git a/public/fonts/pkmnems.ttf b/public/fonts/pkmnems.ttf index 0aa3a19b417..b0b50d0f10f 100644 Binary files a/public/fonts/pkmnems.ttf and b/public/fonts/pkmnems.ttf differ diff --git a/public/images/mystery-encounters/weird_dream_woman.json b/public/images/mystery-encounters/weird_dream_woman.json index 66a9b8d68db..49ebc001d18 100644 --- a/public/images/mystery-encounters/weird_dream_woman.json +++ b/public/images/mystery-encounters/weird_dream_woman.json @@ -5,29 +5,29 @@ "format": "RGBA8888", "size": { "w": 78, - "h": 87 + "h": 86 }, "scale": 1, "frames": [ { "filename": "0001.png", "rotated": false, - "trimmed": true, + "trimmed": false, "sourceSize": { - "w": 80, - "h": 87 + "w": 78, + "h": 86 }, "spriteSourceSize": { - "x": 1, + "x": 0, "y": 0, "w": 78, - "h": 87 + "h": 86 }, "frame": { "x": 0, "y": 0, "w": 78, - "h": 87 + "h": 86 } } ] @@ -36,6 +36,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:d3cce87ee0e3a880d840bffe9373d5d4:7c776d33b75abad1fe36b14a5e5734af:56468b7a2883e66dadcd2af13ebd8010$" + "smartupdate": "$TexturePacker:SmartUpdate:65266da62e9d2953511c0d68ae431345:c1ca63690bed8dd5af71bb443910c830:56468b7a2883e66dadcd2af13ebd8010$" } } diff --git a/public/images/mystery-encounters/weird_dream_woman.png b/public/images/mystery-encounters/weird_dream_woman.png index 1b8d142ed5b..50d04667152 100644 Binary files a/public/images/mystery-encounters/weird_dream_woman.png and b/public/images/mystery-encounters/weird_dream_woman.png differ diff --git a/public/images/pokemon/966-caph-starmobile.json b/public/images/pokemon/966-caph-starmobile.json new file mode 100644 index 00000000000..96c5aada282 --- /dev/null +++ b/public/images/pokemon/966-caph-starmobile.json @@ -0,0 +1,19 @@ +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 0, "w": 94, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 94, "h": 94 }, + "sourceSize": { "w": 120, "h": 94 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "966-caph-starmobile.png", + "format": "RGBA8888", + "size": { "w": 94, "h": 94 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/966-caph-starmobile.png b/public/images/pokemon/966-caph-starmobile.png new file mode 100644 index 00000000000..987782e529e Binary files /dev/null and b/public/images/pokemon/966-caph-starmobile.png differ diff --git a/public/images/pokemon/966-navi-starmobile.json b/public/images/pokemon/966-navi-starmobile.json new file mode 100644 index 00000000000..6a39310af00 --- /dev/null +++ b/public/images/pokemon/966-navi-starmobile.json @@ -0,0 +1,19 @@ +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 0, "w": 94, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 94, "h": 94 }, + "sourceSize": { "w": 120, "h": 94 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "966-navi-starmobile.png", + "format": "RGBA8888", + "size": { "w": 94, "h": 94 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/966-navi-starmobile.png b/public/images/pokemon/966-navi-starmobile.png new file mode 100644 index 00000000000..41d0fd4690c Binary files /dev/null and b/public/images/pokemon/966-navi-starmobile.png differ diff --git a/public/images/pokemon/966-ruchbah-starmobile.json b/public/images/pokemon/966-ruchbah-starmobile.json new file mode 100644 index 00000000000..c75a5630f45 --- /dev/null +++ b/public/images/pokemon/966-ruchbah-starmobile.json @@ -0,0 +1,19 @@ +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 0, "w": 94, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 94, "h": 94 }, + "sourceSize": { "w": 120, "h": 94 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "966-ruchbah-starmobile.png", + "format": "RGBA8888", + "size": { "w": 94, "h": 94 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/966-ruchbah-starmobile.png b/public/images/pokemon/966-ruchbah-starmobile.png new file mode 100644 index 00000000000..765f1fe5eaa Binary files /dev/null and b/public/images/pokemon/966-ruchbah-starmobile.png differ diff --git a/public/images/pokemon/966-schedar-starmobile.json b/public/images/pokemon/966-schedar-starmobile.json new file mode 100644 index 00000000000..59f77f3c975 --- /dev/null +++ b/public/images/pokemon/966-schedar-starmobile.json @@ -0,0 +1,19 @@ +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 0, "w": 94, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 94, "h": 94 }, + "sourceSize": { "w": 120, "h": 94 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "966-schedar-starmobile.png", + "format": "RGBA8888", + "size": { "w": 94, "h": 94 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/966-schedar-starmobile.png b/public/images/pokemon/966-schedar-starmobile.png new file mode 100644 index 00000000000..4cbc60f581f Binary files /dev/null and b/public/images/pokemon/966-schedar-starmobile.png differ diff --git a/public/images/pokemon/966-segin-starmobile.json b/public/images/pokemon/966-segin-starmobile.json new file mode 100644 index 00000000000..98b3938643b --- /dev/null +++ b/public/images/pokemon/966-segin-starmobile.json @@ -0,0 +1,19 @@ +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 0, "w": 94, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 94, "h": 94 }, + "sourceSize": { "w": 120, "h": 94 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "966-segin-starmobile.png", + "format": "RGBA8888", + "size": { "w": 94, "h": 94 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/966-segin-starmobile.png b/public/images/pokemon/966-segin-starmobile.png new file mode 100644 index 00000000000..fab6b1f62ee Binary files /dev/null and b/public/images/pokemon/966-segin-starmobile.png differ diff --git a/public/images/pokemon/back/966-caph-starmobile.json b/public/images/pokemon/back/966-caph-starmobile.json new file mode 100644 index 00000000000..d71eccd11d7 --- /dev/null +++ b/public/images/pokemon/back/966-caph-starmobile.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "966-caph-starmobile.png", + "format": "RGBA8888", + "size": { + "w": 84, + "h": 84 + }, + "scale": 0.333, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 6, + "y": 20, + "w": 84, + "h": 56 + }, + "frame": { + "x": 0, + "y": 0, + "w": 84, + "h": 56 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0226ae22b7a4822d78e38df4af1f59a7:01ce69442faf54e54474cd349cad2f7d:f9a0366e304d666e4262fa0af369d1f4$" + } +} diff --git a/public/images/pokemon/back/966-caph-starmobile.png b/public/images/pokemon/back/966-caph-starmobile.png new file mode 100644 index 00000000000..d1e67365454 Binary files /dev/null and b/public/images/pokemon/back/966-caph-starmobile.png differ diff --git a/public/images/pokemon/back/966-navi-starmobile.json b/public/images/pokemon/back/966-navi-starmobile.json new file mode 100644 index 00000000000..99059aa6edb --- /dev/null +++ b/public/images/pokemon/back/966-navi-starmobile.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "966-navi-starmobile.png", + "format": "RGBA8888", + "size": { + "w": 84, + "h": 84 + }, + "scale": 0.333, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 6, + "y": 20, + "w": 84, + "h": 56 + }, + "frame": { + "x": 0, + "y": 0, + "w": 84, + "h": 56 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0226ae22b7a4822d78e38df4af1f59a7:01ce69442faf54e54474cd349cad2f7d:f9a0366e304d666e4262fa0af369d1f4$" + } +} diff --git a/public/images/pokemon/back/966-navi-starmobile.png b/public/images/pokemon/back/966-navi-starmobile.png new file mode 100644 index 00000000000..d1e67365454 Binary files /dev/null and b/public/images/pokemon/back/966-navi-starmobile.png differ diff --git a/public/images/pokemon/back/966-ruchbah-starmobile.json b/public/images/pokemon/back/966-ruchbah-starmobile.json new file mode 100644 index 00000000000..b3bb8463eac --- /dev/null +++ b/public/images/pokemon/back/966-ruchbah-starmobile.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "966-ruchbah-starmobile.png", + "format": "RGBA8888", + "size": { + "w": 84, + "h": 84 + }, + "scale": 0.333, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 6, + "y": 20, + "w": 84, + "h": 56 + }, + "frame": { + "x": 0, + "y": 0, + "w": 84, + "h": 56 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0226ae22b7a4822d78e38df4af1f59a7:01ce69442faf54e54474cd349cad2f7d:f9a0366e304d666e4262fa0af369d1f4$" + } +} diff --git a/public/images/pokemon/back/966-ruchbah-starmobile.png b/public/images/pokemon/back/966-ruchbah-starmobile.png new file mode 100644 index 00000000000..d1e67365454 Binary files /dev/null and b/public/images/pokemon/back/966-ruchbah-starmobile.png differ diff --git a/public/images/pokemon/back/966-schedar-starmobile.json b/public/images/pokemon/back/966-schedar-starmobile.json new file mode 100644 index 00000000000..9832835b3ce --- /dev/null +++ b/public/images/pokemon/back/966-schedar-starmobile.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "966-schedar-starmobile.png", + "format": "RGBA8888", + "size": { + "w": 84, + "h": 84 + }, + "scale": 0.333, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 6, + "y": 20, + "w": 84, + "h": 56 + }, + "frame": { + "x": 0, + "y": 0, + "w": 84, + "h": 56 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0226ae22b7a4822d78e38df4af1f59a7:01ce69442faf54e54474cd349cad2f7d:f9a0366e304d666e4262fa0af369d1f4$" + } +} diff --git a/public/images/pokemon/back/966-schedar-starmobile.png b/public/images/pokemon/back/966-schedar-starmobile.png new file mode 100644 index 00000000000..d1e67365454 Binary files /dev/null and b/public/images/pokemon/back/966-schedar-starmobile.png differ diff --git a/public/images/pokemon/back/966-segin-starmobile.json b/public/images/pokemon/back/966-segin-starmobile.json new file mode 100644 index 00000000000..75bd4d8f304 --- /dev/null +++ b/public/images/pokemon/back/966-segin-starmobile.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "966-segin-starmobile.png", + "format": "RGBA8888", + "size": { + "w": 84, + "h": 84 + }, + "scale": 0.333, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 6, + "y": 20, + "w": 84, + "h": 56 + }, + "frame": { + "x": 0, + "y": 0, + "w": 84, + "h": 56 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0226ae22b7a4822d78e38df4af1f59a7:01ce69442faf54e54474cd349cad2f7d:f9a0366e304d666e4262fa0af369d1f4$" + } +} diff --git a/public/images/pokemon/back/966-segin-starmobile.png b/public/images/pokemon/back/966-segin-starmobile.png new file mode 100644 index 00000000000..d1e67365454 Binary files /dev/null and b/public/images/pokemon/back/966-segin-starmobile.png differ diff --git a/public/images/pokemon/back/shiny/966-caph-starmobile.json b/public/images/pokemon/back/shiny/966-caph-starmobile.json new file mode 100644 index 00000000000..d71eccd11d7 --- /dev/null +++ b/public/images/pokemon/back/shiny/966-caph-starmobile.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "966-caph-starmobile.png", + "format": "RGBA8888", + "size": { + "w": 84, + "h": 84 + }, + "scale": 0.333, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 6, + "y": 20, + "w": 84, + "h": 56 + }, + "frame": { + "x": 0, + "y": 0, + "w": 84, + "h": 56 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0226ae22b7a4822d78e38df4af1f59a7:01ce69442faf54e54474cd349cad2f7d:f9a0366e304d666e4262fa0af369d1f4$" + } +} diff --git a/public/images/pokemon/back/shiny/966-caph-starmobile.png b/public/images/pokemon/back/shiny/966-caph-starmobile.png new file mode 100644 index 00000000000..64e72d6793f Binary files /dev/null and b/public/images/pokemon/back/shiny/966-caph-starmobile.png differ diff --git a/public/images/pokemon/back/shiny/966-navi-starmobile.json b/public/images/pokemon/back/shiny/966-navi-starmobile.json new file mode 100644 index 00000000000..99059aa6edb --- /dev/null +++ b/public/images/pokemon/back/shiny/966-navi-starmobile.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "966-navi-starmobile.png", + "format": "RGBA8888", + "size": { + "w": 84, + "h": 84 + }, + "scale": 0.333, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 6, + "y": 20, + "w": 84, + "h": 56 + }, + "frame": { + "x": 0, + "y": 0, + "w": 84, + "h": 56 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0226ae22b7a4822d78e38df4af1f59a7:01ce69442faf54e54474cd349cad2f7d:f9a0366e304d666e4262fa0af369d1f4$" + } +} diff --git a/public/images/pokemon/back/shiny/966-navi-starmobile.png b/public/images/pokemon/back/shiny/966-navi-starmobile.png new file mode 100644 index 00000000000..64e72d6793f Binary files /dev/null and b/public/images/pokemon/back/shiny/966-navi-starmobile.png differ diff --git a/public/images/pokemon/back/shiny/966-ruchbah-starmobile.json b/public/images/pokemon/back/shiny/966-ruchbah-starmobile.json new file mode 100644 index 00000000000..b3bb8463eac --- /dev/null +++ b/public/images/pokemon/back/shiny/966-ruchbah-starmobile.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "966-ruchbah-starmobile.png", + "format": "RGBA8888", + "size": { + "w": 84, + "h": 84 + }, + "scale": 0.333, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 6, + "y": 20, + "w": 84, + "h": 56 + }, + "frame": { + "x": 0, + "y": 0, + "w": 84, + "h": 56 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0226ae22b7a4822d78e38df4af1f59a7:01ce69442faf54e54474cd349cad2f7d:f9a0366e304d666e4262fa0af369d1f4$" + } +} diff --git a/public/images/pokemon/back/shiny/966-ruchbah-starmobile.png b/public/images/pokemon/back/shiny/966-ruchbah-starmobile.png new file mode 100644 index 00000000000..64e72d6793f Binary files /dev/null and b/public/images/pokemon/back/shiny/966-ruchbah-starmobile.png differ diff --git a/public/images/pokemon/back/shiny/966-schedar-starmobile.json b/public/images/pokemon/back/shiny/966-schedar-starmobile.json new file mode 100644 index 00000000000..9832835b3ce --- /dev/null +++ b/public/images/pokemon/back/shiny/966-schedar-starmobile.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "966-schedar-starmobile.png", + "format": "RGBA8888", + "size": { + "w": 84, + "h": 84 + }, + "scale": 0.333, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 6, + "y": 20, + "w": 84, + "h": 56 + }, + "frame": { + "x": 0, + "y": 0, + "w": 84, + "h": 56 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0226ae22b7a4822d78e38df4af1f59a7:01ce69442faf54e54474cd349cad2f7d:f9a0366e304d666e4262fa0af369d1f4$" + } +} diff --git a/public/images/pokemon/back/shiny/966-schedar-starmobile.png b/public/images/pokemon/back/shiny/966-schedar-starmobile.png new file mode 100644 index 00000000000..64e72d6793f Binary files /dev/null and b/public/images/pokemon/back/shiny/966-schedar-starmobile.png differ diff --git a/public/images/pokemon/back/shiny/966-segin-starmobile.json b/public/images/pokemon/back/shiny/966-segin-starmobile.json new file mode 100644 index 00000000000..75bd4d8f304 --- /dev/null +++ b/public/images/pokemon/back/shiny/966-segin-starmobile.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "966-segin-starmobile.png", + "format": "RGBA8888", + "size": { + "w": 84, + "h": 84 + }, + "scale": 0.333, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 6, + "y": 20, + "w": 84, + "h": 56 + }, + "frame": { + "x": 0, + "y": 0, + "w": 84, + "h": 56 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0226ae22b7a4822d78e38df4af1f59a7:01ce69442faf54e54474cd349cad2f7d:f9a0366e304d666e4262fa0af369d1f4$" + } +} diff --git a/public/images/pokemon/back/shiny/966-segin-starmobile.png b/public/images/pokemon/back/shiny/966-segin-starmobile.png new file mode 100644 index 00000000000..64e72d6793f Binary files /dev/null and b/public/images/pokemon/back/shiny/966-segin-starmobile.png differ diff --git a/public/images/pokemon/icons/9/966-caph-starmobile.png b/public/images/pokemon/icons/9/966-caph-starmobile.png new file mode 100644 index 00000000000..fba351495bd Binary files /dev/null and b/public/images/pokemon/icons/9/966-caph-starmobile.png differ diff --git a/public/images/pokemon/icons/9/966-navi-starmobile.png b/public/images/pokemon/icons/9/966-navi-starmobile.png new file mode 100644 index 00000000000..fba351495bd Binary files /dev/null and b/public/images/pokemon/icons/9/966-navi-starmobile.png differ diff --git a/public/images/pokemon/icons/9/966-ruchbah-starmobile.png b/public/images/pokemon/icons/9/966-ruchbah-starmobile.png new file mode 100644 index 00000000000..fba351495bd Binary files /dev/null and b/public/images/pokemon/icons/9/966-ruchbah-starmobile.png differ diff --git a/public/images/pokemon/icons/9/966-schedar-starmobile.png b/public/images/pokemon/icons/9/966-schedar-starmobile.png new file mode 100644 index 00000000000..fba351495bd Binary files /dev/null and b/public/images/pokemon/icons/9/966-schedar-starmobile.png differ diff --git a/public/images/pokemon/icons/9/966-segin-starmobile.png b/public/images/pokemon/icons/9/966-segin-starmobile.png new file mode 100644 index 00000000000..fba351495bd Binary files /dev/null and b/public/images/pokemon/icons/9/966-segin-starmobile.png differ diff --git a/public/images/pokemon/icons/9/966s-caph-starmobile.png b/public/images/pokemon/icons/9/966s-caph-starmobile.png new file mode 100644 index 00000000000..e54e5c90e4a Binary files /dev/null and b/public/images/pokemon/icons/9/966s-caph-starmobile.png differ diff --git a/public/images/pokemon/icons/9/966s-navi-starmobile.png b/public/images/pokemon/icons/9/966s-navi-starmobile.png new file mode 100644 index 00000000000..e54e5c90e4a Binary files /dev/null and b/public/images/pokemon/icons/9/966s-navi-starmobile.png differ diff --git a/public/images/pokemon/icons/9/966s-ruchbah-starmobile.png b/public/images/pokemon/icons/9/966s-ruchbah-starmobile.png new file mode 100644 index 00000000000..e54e5c90e4a Binary files /dev/null and b/public/images/pokemon/icons/9/966s-ruchbah-starmobile.png differ diff --git a/public/images/pokemon/icons/9/966s-schedar-starmobile.png b/public/images/pokemon/icons/9/966s-schedar-starmobile.png new file mode 100644 index 00000000000..e54e5c90e4a Binary files /dev/null and b/public/images/pokemon/icons/9/966s-schedar-starmobile.png differ diff --git a/public/images/pokemon/icons/9/966s-segin-starmobile.png b/public/images/pokemon/icons/9/966s-segin-starmobile.png new file mode 100644 index 00000000000..e54e5c90e4a Binary files /dev/null and b/public/images/pokemon/icons/9/966s-segin-starmobile.png differ diff --git a/public/images/pokemon/shiny/966-caph-starmobile.json b/public/images/pokemon/shiny/966-caph-starmobile.json new file mode 100644 index 00000000000..96c5aada282 --- /dev/null +++ b/public/images/pokemon/shiny/966-caph-starmobile.json @@ -0,0 +1,19 @@ +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 0, "w": 94, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 94, "h": 94 }, + "sourceSize": { "w": 120, "h": 94 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "966-caph-starmobile.png", + "format": "RGBA8888", + "size": { "w": 94, "h": 94 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/shiny/966-caph-starmobile.png b/public/images/pokemon/shiny/966-caph-starmobile.png new file mode 100644 index 00000000000..6107a426ff6 Binary files /dev/null and b/public/images/pokemon/shiny/966-caph-starmobile.png differ diff --git a/public/images/pokemon/shiny/966-navi-starmobile.json b/public/images/pokemon/shiny/966-navi-starmobile.json new file mode 100644 index 00000000000..6a39310af00 --- /dev/null +++ b/public/images/pokemon/shiny/966-navi-starmobile.json @@ -0,0 +1,19 @@ +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 0, "w": 94, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 94, "h": 94 }, + "sourceSize": { "w": 120, "h": 94 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "966-navi-starmobile.png", + "format": "RGBA8888", + "size": { "w": 94, "h": 94 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/shiny/966-navi-starmobile.png b/public/images/pokemon/shiny/966-navi-starmobile.png new file mode 100644 index 00000000000..74999d4fa13 Binary files /dev/null and b/public/images/pokemon/shiny/966-navi-starmobile.png differ diff --git a/public/images/pokemon/shiny/966-ruchbah-starmobile.json b/public/images/pokemon/shiny/966-ruchbah-starmobile.json new file mode 100644 index 00000000000..c75a5630f45 --- /dev/null +++ b/public/images/pokemon/shiny/966-ruchbah-starmobile.json @@ -0,0 +1,19 @@ +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 0, "w": 94, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 94, "h": 94 }, + "sourceSize": { "w": 120, "h": 94 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "966-ruchbah-starmobile.png", + "format": "RGBA8888", + "size": { "w": 94, "h": 94 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/shiny/966-ruchbah-starmobile.png b/public/images/pokemon/shiny/966-ruchbah-starmobile.png new file mode 100644 index 00000000000..9de01ac6a73 Binary files /dev/null and b/public/images/pokemon/shiny/966-ruchbah-starmobile.png differ diff --git a/public/images/pokemon/shiny/966-schedar-starmobile.json b/public/images/pokemon/shiny/966-schedar-starmobile.json new file mode 100644 index 00000000000..59f77f3c975 --- /dev/null +++ b/public/images/pokemon/shiny/966-schedar-starmobile.json @@ -0,0 +1,19 @@ +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 0, "w": 94, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 94, "h": 94 }, + "sourceSize": { "w": 120, "h": 94 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "966-schedar-starmobile.png", + "format": "RGBA8888", + "size": { "w": 94, "h": 94 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/shiny/966-schedar-starmobile.png b/public/images/pokemon/shiny/966-schedar-starmobile.png new file mode 100644 index 00000000000..79033bb123c Binary files /dev/null and b/public/images/pokemon/shiny/966-schedar-starmobile.png differ diff --git a/public/images/pokemon/shiny/966-segin-starmobile.json b/public/images/pokemon/shiny/966-segin-starmobile.json new file mode 100644 index 00000000000..98b3938643b --- /dev/null +++ b/public/images/pokemon/shiny/966-segin-starmobile.json @@ -0,0 +1,19 @@ +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 0, "w": 94, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 94, "h": 94 }, + "sourceSize": { "w": 120, "h": 94 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "966-segin-starmobile.png", + "format": "RGBA8888", + "size": { "w": 94, "h": 94 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/shiny/966-segin-starmobile.png b/public/images/pokemon/shiny/966-segin-starmobile.png new file mode 100644 index 00000000000..f4cab89a203 Binary files /dev/null and b/public/images/pokemon/shiny/966-segin-starmobile.png differ diff --git a/public/images/pokemon_icons_9.json b/public/images/pokemon_icons_9.json index 26e28eedae0..01994a41a02 100644 --- a/public/images/pokemon_icons_9.json +++ b/public/images/pokemon_icons_9.json @@ -4,8 +4,8 @@ "image": "pokemon_icons_9.png", "format": "RGBA8888", "size": { - "w": 252, - "h": 591 + "w": 255, + "h": 646 }, "scale": 1, "frames": [ @@ -382,7 +382,7 @@ }, "frame": { "x": 0, - "y": 270, + "y": 300, "w": 30, "h": 30 } @@ -429,27 +429,6 @@ "h": 27 } }, - { - "filename": "8901", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 30, - "h": 28 - }, - "frame": { - "x": 222, - "y": 0, - "w": 30, - "h": 28 - } - }, { "filename": "1020", "rotated": false, @@ -528,12 +507,33 @@ "h": 26 }, "frame": { - "x": 126, - "y": 28, + "x": 222, + "y": 0, "w": 32, "h": 26 } }, + { + "filename": "8901", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 30, + "h": 28 + }, + "frame": { + "x": 0, + "y": 330, + "w": 30, + "h": 28 + } + }, { "filename": "8901s", "rotated": false, @@ -550,7 +550,7 @@ }, "frame": { "x": 0, - "y": 300, + "y": 358, "w": 30, "h": 28 } @@ -571,7 +571,7 @@ }, "frame": { "x": 0, - "y": 328, + "y": 386, "w": 27, "h": 30 } @@ -592,7 +592,7 @@ }, "frame": { "x": 0, - "y": 358, + "y": 416, "w": 27, "h": 30 } @@ -611,6 +611,27 @@ "w": 32, "h": 25 }, + "frame": { + "x": 126, + "y": 28, + "w": 32, + "h": 25 + } + }, + { + "filename": "984s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 4, + "y": 3, + "w": 32, + "h": 25 + }, "frame": { "x": 158, "y": 27, @@ -619,7 +640,7 @@ } }, { - "filename": "984s", + "filename": "1014", "rotated": false, "trimmed": true, "sourceSize": { @@ -640,7 +661,7 @@ } }, { - "filename": "992", + "filename": "1014s", "rotated": false, "trimmed": true, "sourceSize": { @@ -648,16 +669,16 @@ "h": 30 }, "spriteSourceSize": { - "x": 6, - "y": 2, - "w": 30, - "h": 26 + "x": 4, + "y": 3, + "w": 32, + "h": 25 }, "frame": { "x": 222, - "y": 28, - "w": 30, - "h": 26 + "y": 26, + "w": 32, + "h": 25 } }, { @@ -676,7 +697,7 @@ }, "frame": { "x": 0, - "y": 388, + "y": 446, "w": 27, "h": 29 } @@ -697,7 +718,7 @@ }, "frame": { "x": 0, - "y": 417, + "y": 475, "w": 27, "h": 29 } @@ -718,7 +739,7 @@ }, "frame": { "x": 0, - "y": 446, + "y": 504, "w": 29, "h": 28 } @@ -739,7 +760,7 @@ }, "frame": { "x": 0, - "y": 474, + "y": 532, "w": 29, "h": 28 } @@ -760,7 +781,7 @@ }, "frame": { "x": 0, - "y": 502, + "y": 560, "w": 29, "h": 27 } @@ -781,7 +802,7 @@ }, "frame": { "x": 0, - "y": 529, + "y": 587, "w": 29, "h": 27 } @@ -802,7 +823,7 @@ }, "frame": { "x": 0, - "y": 556, + "y": 614, "w": 28, "h": 28 } @@ -828,90 +849,6 @@ "h": 21 } }, - { - "filename": "975s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 4, - "y": 7, - "w": 32, - "h": 21 - }, - "frame": { - "x": 126, - "y": 54, - "w": 32, - "h": 21 - } - }, - { - "filename": "1014", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 4, - "y": 3, - "w": 32, - "h": 25 - }, - "frame": { - "x": 158, - "y": 52, - "w": 32, - "h": 25 - } - }, - { - "filename": "1014s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 4, - "y": 3, - "w": 32, - "h": 25 - }, - "frame": { - "x": 190, - "y": 52, - "w": 32, - "h": 25 - } - }, - { - "filename": "992s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 6, - "y": 2, - "w": 30, - "h": 26 - }, - "frame": { - "x": 222, - "y": 54, - "w": 30, - "h": 26 - } - }, { "filename": "1024-terastal", "rotated": false, @@ -926,11 +863,95 @@ "w": 32, "h": 22 }, + "frame": { + "x": 126, + "y": 53, + "w": 32, + "h": 22 + } + }, + { + "filename": "1025", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 4, + "y": 4, + "w": 32, + "h": 24 + }, + "frame": { + "x": 158, + "y": 52, + "w": 32, + "h": 24 + } + }, + { + "filename": "1025s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 4, + "y": 4, + "w": 32, + "h": 24 + }, + "frame": { + "x": 190, + "y": 52, + "w": 32, + "h": 24 + } + }, + { + "filename": "992", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 6, + "y": 2, + "w": 30, + "h": 26 + }, + "frame": { + "x": 222, + "y": 51, + "w": 30, + "h": 26 + } + }, + { + "filename": "975s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 4, + "y": 7, + "w": 32, + "h": 21 + }, "frame": { "x": 93, "y": 75, "w": 32, - "h": 22 + "h": 21 } }, { @@ -955,7 +976,7 @@ } }, { - "filename": "1025", + "filename": "992s", "rotated": false, "trimmed": true, "sourceSize": { @@ -963,37 +984,16 @@ "h": 30 }, "spriteSourceSize": { - "x": 4, - "y": 4, - "w": 32, - "h": 24 + "x": 6, + "y": 2, + "w": 30, + "h": 26 }, "frame": { "x": 157, - "y": 77, - "w": 32, - "h": 24 - } - }, - { - "filename": "1025s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 4, - "y": 4, - "w": 32, - "h": 24 - }, - "frame": { - "x": 189, - "y": 77, - "w": 32, - "h": 24 + "y": 76, + "w": 30, + "h": 26 } }, { @@ -1011,8 +1011,29 @@ "h": 25 }, "frame": { - "x": 221, - "y": 80, + "x": 187, + "y": 76, + "w": 30, + "h": 25 + } + }, + { + "filename": "993s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 6, + "y": 2, + "w": 30, + "h": 25 + }, + "frame": { + "x": 217, + "y": 77, "w": 30, "h": 25 } @@ -1033,7 +1054,7 @@ }, "frame": { "x": 27, - "y": 328, + "y": 386, "w": 23, "h": 30 } @@ -1054,7 +1075,7 @@ }, "frame": { "x": 27, - "y": 358, + "y": 416, "w": 23, "h": 30 } @@ -1075,7 +1096,7 @@ }, "frame": { "x": 27, - "y": 388, + "y": 446, "w": 25, "h": 30 } @@ -1096,7 +1117,7 @@ }, "frame": { "x": 27, - "y": 418, + "y": 476, "w": 25, "h": 28 } @@ -1117,7 +1138,7 @@ }, "frame": { "x": 29, - "y": 446, + "y": 504, "w": 25, "h": 30 } @@ -1138,7 +1159,7 @@ }, "frame": { "x": 29, - "y": 476, + "y": 534, "w": 25, "h": 28 } @@ -1159,7 +1180,7 @@ }, "frame": { "x": 29, - "y": 504, + "y": 562, "w": 26, "h": 28 } @@ -1180,7 +1201,7 @@ }, "frame": { "x": 29, - "y": 532, + "y": 590, "w": 27, "h": 26 } @@ -1201,32 +1222,11 @@ }, "frame": { "x": 28, - "y": 558, + "y": 616, "w": 28, "h": 28 } }, - { - "filename": "993s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 6, - "y": 2, - "w": 30, - "h": 25 - }, - "frame": { - "x": 62, - "y": 86, - "w": 30, - "h": 25 - } - }, { "filename": "924", "rotated": false, @@ -1242,8 +1242,8 @@ "h": 20 }, "frame": { - "x": 92, - "y": 97, + "x": 62, + "y": 86, "w": 29, "h": 20 } @@ -1263,8 +1263,8 @@ "h": 20 }, "frame": { - "x": 121, - "y": 97, + "x": 61, + "y": 106, "w": 29, "h": 20 } @@ -1284,8 +1284,8 @@ "h": 22 }, "frame": { - "x": 150, - "y": 101, + "x": 91, + "y": 96, "w": 29, "h": 22 } @@ -1305,33 +1305,12 @@ "h": 22 }, "frame": { - "x": 179, - "y": 101, + "x": 120, + "y": 97, "w": 29, "h": 22 } }, - { - "filename": "935", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 13, - "y": 7, - "w": 13, - "h": 21 - }, - "frame": { - "x": 208, - "y": 101, - "w": 13, - "h": 21 - } - }, { "filename": "925-three", "rotated": false, @@ -1347,8 +1326,8 @@ "h": 20 }, "frame": { - "x": 221, - "y": 105, + "x": 149, + "y": 102, "w": 29, "h": 20 } @@ -1368,54 +1347,12 @@ "h": 20 }, "frame": { - "x": 61, - "y": 111, + "x": 90, + "y": 118, "w": 29, "h": 20 } }, - { - "filename": "976", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 5, - "y": 10, - "w": 29, - "h": 18 - }, - "frame": { - "x": 90, - "y": 117, - "w": 29, - "h": 18 - } - }, - { - "filename": "976s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 5, - "y": 10, - "w": 29, - "h": 18 - }, - "frame": { - "x": 119, - "y": 117, - "w": 29, - "h": 18 - } - }, { "filename": "1022", "rotated": false, @@ -1431,8 +1368,8 @@ "h": 25 }, "frame": { - "x": 148, - "y": 123, + "x": 119, + "y": 119, "w": 29, "h": 25 } @@ -1452,12 +1389,33 @@ "h": 25 }, "frame": { - "x": 177, - "y": 123, + "x": 148, + "y": 122, "w": 29, "h": 25 } }, + { + "filename": "976", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 5, + "y": 10, + "w": 29, + "h": 18 + }, + "frame": { + "x": 31, + "y": 118, + "w": 29, + "h": 18 + } + }, { "filename": "8128-blaze", "rotated": false, @@ -1473,54 +1431,12 @@ "h": 27 }, "frame": { - "x": 206, - "y": 125, + "x": 30, + "y": 136, "w": 29, "h": 27 } }, - { - "filename": "913", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 5, - "w": 17, - "h": 23 - }, - "frame": { - "x": 235, - "y": 125, - "w": 17, - "h": 23 - } - }, - { - "filename": "913s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 5, - "w": 17, - "h": 23 - }, - "frame": { - "x": 235, - "y": 148, - "w": 17, - "h": 23 - } - }, { "filename": "8128s-blaze", "rotated": false, @@ -1536,8 +1452,8 @@ "h": 27 }, "frame": { - "x": 31, - "y": 118, + "x": 30, + "y": 163, "w": 29, "h": 27 } @@ -1558,7 +1474,7 @@ }, "frame": { "x": 30, - "y": 145, + "y": 190, "w": 27, "h": 28 } @@ -1579,7 +1495,7 @@ }, "frame": { "x": 30, - "y": 173, + "y": 218, "w": 27, "h": 28 } @@ -1600,7 +1516,7 @@ }, "frame": { "x": 30, - "y": 201, + "y": 246, "w": 26, "h": 28 } @@ -1621,7 +1537,7 @@ }, "frame": { "x": 30, - "y": 229, + "y": 274, "w": 27, "h": 26 } @@ -1642,7 +1558,7 @@ }, "frame": { "x": 30, - "y": 255, + "y": 300, "w": 25, "h": 27 } @@ -1663,13 +1579,13 @@ }, "frame": { "x": 30, - "y": 282, + "y": 327, "w": 25, "h": 27 } }, { - "filename": "916", + "filename": "964-hero", "rotated": false, "trimmed": true, "sourceSize": { @@ -1677,20 +1593,20 @@ "h": 30 }, "spriteSourceSize": { - "x": 7, - "y": 9, - "w": 25, - "h": 19 + "x": 9, + "y": 0, + "w": 22, + "h": 29 }, "frame": { "x": 30, - "y": 309, - "w": 25, - "h": 19 + "y": 354, + "w": 22, + "h": 29 } }, { - "filename": "911", + "filename": "976s", "rotated": false, "trimmed": true, "sourceSize": { @@ -1698,16 +1614,16 @@ "h": 30 }, "spriteSourceSize": { - "x": 6, - "y": 5, - "w": 28, - "h": 23 + "x": 5, + "y": 10, + "w": 29, + "h": 18 }, "frame": { "x": 60, - "y": 131, - "w": 28, - "h": 23 + "y": 126, + "w": 29, + "h": 18 } }, { @@ -1724,9 +1640,51 @@ "w": 28, "h": 24 }, + "frame": { + "x": 59, + "y": 144, + "w": 28, + "h": 24 + } + }, + { + "filename": "911", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 6, + "y": 5, + "w": 28, + "h": 23 + }, + "frame": { + "x": 59, + "y": 168, + "w": 28, + "h": 23 + } + }, + { + "filename": "8128s-aqua", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 5, + "w": 28, + "h": 24 + }, "frame": { "x": 57, - "y": 154, + "y": 191, "w": 28, "h": 24 } @@ -1747,13 +1705,13 @@ }, "frame": { "x": 57, - "y": 178, + "y": 215, "w": 28, "h": 23 } }, { - "filename": "968", + "filename": "950", "rotated": false, "trimmed": true, "sourceSize": { @@ -1761,37 +1719,58 @@ "h": 30 }, "spriteSourceSize": { - "x": 8, - "y": 0, - "w": 23, - "h": 28 - }, - "frame": { - "x": 56, - "y": 201, - "w": 23, - "h": 28 - } - }, - { - "filename": "964-hero", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 0, - "w": 22, - "h": 29 + "x": 6, + "y": 11, + "w": 28, + "h": 17 }, "frame": { "x": 57, - "y": 229, - "w": 22, - "h": 29 + "y": 238, + "w": 28, + "h": 17 + } + }, + { + "filename": "916", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 9, + "w": 25, + "h": 19 + }, + "frame": { + "x": 56, + "y": 255, + "w": 25, + "h": 19 + } + }, + { + "filename": "998", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 2, + "w": 25, + "h": 26 + }, + "frame": { + "x": 57, + "y": 274, + "w": 25, + "h": 26 } }, { @@ -1810,13 +1789,55 @@ }, "frame": { "x": 55, - "y": 258, + "y": 300, "w": 22, "h": 29 } }, { - "filename": "999", + "filename": "914", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 3, + "w": 24, + "h": 25 + }, + "frame": { + "x": 55, + "y": 329, + "w": 24, + "h": 25 + } + }, + { + "filename": "968", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 23, + "h": 28 + }, + "frame": { + "x": 52, + "y": 354, + "w": 23, + "h": 28 + } + }, + { + "filename": "954", "rotated": false, "trimmed": true, "sourceSize": { @@ -1826,16 +1847,100 @@ "spriteSourceSize": { "x": 9, "y": 0, - "w": 22, + "w": 21, "h": 29 }, "frame": { - "x": 55, - "y": 287, - "w": 22, + "x": 77, + "y": 300, + "w": 21, "h": 29 } }, + { + "filename": "908", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 2, + "w": 20, + "h": 26 + }, + "frame": { + "x": 79, + "y": 329, + "w": 20, + "h": 26 + } + }, + { + "filename": "1016", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 1, + "w": 24, + "h": 27 + }, + "frame": { + "x": 75, + "y": 355, + "w": 24, + "h": 27 + } + }, + { + "filename": "950s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 6, + "y": 11, + "w": 28, + "h": 17 + }, + "frame": { + "x": 89, + "y": 138, + "w": 28, + "h": 17 + } + }, + { + "filename": "968s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 23, + "h": 28 + }, + "frame": { + "x": 87, + "y": 155, + "w": 23, + "h": 28 + } + }, { "filename": "990", "rotated": false, @@ -1851,8 +1956,8 @@ "h": 23 }, "frame": { - "x": 88, - "y": 135, + "x": 117, + "y": 144, "w": 28, "h": 23 } @@ -1872,33 +1977,12 @@ "h": 23 }, "frame": { - "x": 116, - "y": 135, + "x": 145, + "y": 147, "w": 28, "h": 23 } }, - { - "filename": "8128s-aqua", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 5, - "w": 28, - "h": 24 - }, - "frame": { - "x": 85, - "y": 158, - "w": 28, - "h": 24 - } - }, { "filename": "972", "rotated": false, @@ -1914,8 +1998,8 @@ "h": 22 }, "frame": { - "x": 113, - "y": 158, + "x": 110, + "y": 167, "w": 27, "h": 22 } @@ -1935,14 +2019,14 @@ "h": 22 }, "frame": { - "x": 85, - "y": 182, + "x": 137, + "y": 170, "w": 27, "h": 22 } }, { - "filename": "968s", + "filename": "947", "rotated": false, "trimmed": true, "sourceSize": { @@ -1951,36 +2035,15 @@ }, "spriteSourceSize": { "x": 8, - "y": 0, + "y": 6, "w": 23, - "h": 28 + "h": 22 }, "frame": { - "x": 79, - "y": 204, + "x": 87, + "y": 183, "w": 23, - "h": 28 - } - }, - { - "filename": "998", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 2, - "w": 25, - "h": 26 - }, - "frame": { - "x": 79, - "y": 232, - "w": 25, - "h": 26 + "h": 22 } }, { @@ -1998,14 +2061,14 @@ "h": 26 }, "frame": { - "x": 77, - "y": 258, + "x": 85, + "y": 205, "w": 25, "h": 26 } }, { - "filename": "999s", + "filename": "985", "rotated": false, "trimmed": true, "sourceSize": { @@ -2014,120 +2077,15 @@ }, "spriteSourceSize": { "x": 9, - "y": 0, - "w": 22, - "h": 29 + "y": 7, + "w": 27, + "h": 21 }, "frame": { - "x": 77, - "y": 284, - "w": 22, - "h": 29 - } - }, - { - "filename": "936", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 0, - "w": 20, - "h": 28 - }, - "frame": { - "x": 102, - "y": 204, - "w": 20, - "h": 28 - } - }, - { - "filename": "908", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 2, - "w": 20, - "h": 26 - }, - "frame": { - "x": 104, - "y": 232, - "w": 20, - "h": 26 - } - }, - { - "filename": "982", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 2, - "w": 22, - "h": 26 - }, - "frame": { - "x": 102, - "y": 258, - "w": 22, - "h": 26 - } - }, - { - "filename": "1016", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 1, - "w": 24, - "h": 27 - }, - "frame": { - "x": 99, - "y": 284, - "w": 24, - "h": 27 - } - }, - { - "filename": "1017-hearthflame-mask", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 4, - "w": 25, - "h": 24 - }, - "frame": { - "x": 113, - "y": 180, - "w": 25, - "h": 24 + "x": 110, + "y": 189, + "w": 27, + "h": 21 } }, { @@ -2145,77 +2103,14 @@ "h": 24 }, "frame": { - "x": 113, - "y": 180, + "x": 85, + "y": 231, "w": 25, "h": 24 } }, { - "filename": "1017s-hearthflame-mask", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 4, - "w": 25, - "h": 24 - }, - "frame": { - "x": 113, - "y": 180, - "w": 25, - "h": 24 - } - }, - { - "filename": "1017s-hearthflame-mask-tera", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 4, - "w": 25, - "h": 24 - }, - "frame": { - "x": 113, - "y": 180, - "w": 25, - "h": 24 - } - }, - { - "filename": "936s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 0, - "w": 20, - "h": 28 - }, - "frame": { - "x": 122, - "y": 204, - "w": 20, - "h": 28 - } - }, - { - "filename": "954", + "filename": "999", "rotated": false, "trimmed": true, "sourceSize": { @@ -2225,18 +2120,18 @@ "spriteSourceSize": { "x": 9, "y": 0, - "w": 21, + "w": 22, "h": 29 }, "frame": { - "x": 124, - "y": 232, - "w": 21, + "x": 110, + "y": 210, + "w": 22, "h": 29 } }, { - "filename": "914", + "filename": "916s", "rotated": false, "trimmed": true, "sourceSize": { @@ -2244,57 +2139,57 @@ "h": 30 }, "spriteSourceSize": { - "x": 8, - "y": 3, - "w": 24, - "h": 25 - }, - "frame": { - "x": 124, - "y": 261, - "w": 24, - "h": 25 - } - }, - { - "filename": "914s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 3, - "w": 24, - "h": 25 - }, - "frame": { - "x": 123, - "y": 286, - "w": 24, - "h": 25 - } - }, - { - "filename": "916-female", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 6, - "y": 7, + "x": 7, + "y": 9, "w": 25, + "h": 19 + }, + "frame": { + "x": 81, + "y": 255, + "w": 25, + "h": 19 + } + }, + { + "filename": "982", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 2, + "w": 22, + "h": 26 + }, + "frame": { + "x": 82, + "y": 274, + "w": 22, + "h": 26 + } + }, + { + "filename": "985s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 27, "h": 21 }, "frame": { - "x": 55, - "y": 316, - "w": 25, + "x": 137, + "y": 192, + "w": 27, "h": 21 } }, @@ -2313,14 +2208,182 @@ "h": 27 }, "frame": { - "x": 50, - "y": 337, + "x": 132, + "y": 213, "w": 24, "h": 27 } }, { - "filename": "987", + "filename": "917", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 6, + "w": 22, + "h": 22 + }, + "frame": { + "x": 110, + "y": 239, + "w": 22, + "h": 22 + } + }, + { + "filename": "914s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 3, + "w": 24, + "h": 25 + }, + "frame": { + "x": 132, + "y": 240, + "w": 24, + "h": 25 + } + }, + { + "filename": "956", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 6, + "w": 26, + "h": 22 + }, + "frame": { + "x": 106, + "y": 261, + "w": 26, + "h": 22 + } + }, + { + "filename": "916-female", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 6, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 132, + "y": 265, + "w": 25, + "h": 21 + } + }, + { + "filename": "1002", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 6, + "y": 7, + "w": 27, + "h": 21 + }, + "frame": { + "x": 104, + "y": 283, + "w": 27, + "h": 21 + } + }, + { + "filename": "956s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 6, + "w": 26, + "h": 22 + }, + "frame": { + "x": 131, + "y": 286, + "w": 26, + "h": 22 + } + }, + { + "filename": "1017-hearthflame-mask", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 4, + "w": 25, + "h": 24 + }, + "frame": { + "x": 98, + "y": 304, + "w": 25, + "h": 24 + } + }, + { + "filename": "999s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 0, + "w": 22, + "h": 29 + }, + "frame": { + "x": 99, + "y": 328, + "w": 22, + "h": 29 + } + }, + { + "filename": "982s", "rotated": false, "trimmed": true, "sourceSize": { @@ -2329,15 +2392,36 @@ }, "spriteSourceSize": { "x": 10, - "y": 4, - "w": 24, - "h": 24 + "y": 2, + "w": 22, + "h": 26 }, "frame": { - "x": 50, - "y": 364, - "w": 24, - "h": 24 + "x": 99, + "y": 357, + "w": 22, + "h": 26 + } + }, + { + "filename": "1002s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 6, + "y": 7, + "w": 27, + "h": 21 + }, + "frame": { + "x": 123, + "y": 308, + "w": 27, + "h": 21 } }, { @@ -2355,8 +2439,8 @@ "h": 29 }, "frame": { - "x": 52, - "y": 388, + "x": 121, + "y": 329, "w": 21, "h": 29 } @@ -2376,14 +2460,14 @@ "h": 27 }, "frame": { - "x": 52, - "y": 417, + "x": 121, + "y": 358, "w": 22, "h": 27 } }, { - "filename": "1023", + "filename": "936", "rotated": false, "trimmed": true, "sourceSize": { @@ -2392,19 +2476,19 @@ }, "spriteSourceSize": { "x": 10, - "y": 1, + "y": 0, "w": 20, "h": 28 }, "frame": { - "x": 54, - "y": 444, + "x": 164, + "y": 170, "w": 20, "h": 28 } }, { - "filename": "1023s", + "filename": "913", "rotated": false, "trimmed": true, "sourceSize": { @@ -2412,583 +2496,16 @@ "h": 30 }, "spriteSourceSize": { - "x": 10, - "y": 1, - "w": 20, - "h": 28 + "x": 11, + "y": 5, + "w": 17, + "h": 23 }, "frame": { - "x": 54, - "y": 472, - "w": 20, - "h": 28 - } - }, - { - "filename": "1000s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 22, - "h": 27 - }, - "frame": { - "x": 55, - "y": 500, - "w": 22, - "h": 27 - } - }, - { - "filename": "1006", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 1, - "w": 22, - "h": 27 - }, - "frame": { - "x": 56, - "y": 527, - "w": 22, - "h": 27 - } - }, - { - "filename": "1006s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 1, - "w": 22, - "h": 27 - }, - "frame": { - "x": 56, - "y": 554, - "w": 22, - "h": 27 - } - }, - { - "filename": "908s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 2, - "w": 20, - "h": 26 - }, - "frame": { - "x": 80, - "y": 313, - "w": 20, - "h": 26 - } - }, - { - "filename": "956", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 6, - "w": 26, - "h": 22 - }, - "frame": { - "x": 100, - "y": 311, - "w": 26, - "h": 22 - } - }, - { - "filename": "917", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 6, - "w": 22, - "h": 22 - }, - "frame": { - "x": 126, - "y": 311, - "w": 22, - "h": 22 - } - }, - { - "filename": "956s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 6, - "w": 26, - "h": 22 - }, - "frame": { - "x": 74, - "y": 339, - "w": 26, - "h": 22 - } - }, - { - "filename": "982s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 2, - "w": 22, - "h": 26 - }, - "frame": { - "x": 100, - "y": 333, - "w": 22, - "h": 26 - } - }, - { - "filename": "987s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 74, - "y": 361, - "w": 24, - "h": 24 - } - }, - { - "filename": "1012-artisan", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 6, - "w": 26, - "h": 22 - }, - "frame": { - "x": 122, - "y": 333, - "w": 26, - "h": 22 - } - }, - { - "filename": "1012-counterfeit", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 6, - "w": 26, - "h": 22 - }, - "frame": { - "x": 122, - "y": 333, - "w": 26, - "h": 22 - } - }, - { - "filename": "950", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 6, - "y": 11, - "w": 28, - "h": 17 - }, - "frame": { - "x": 144, - "y": 148, - "w": 28, - "h": 17 - } - }, - { - "filename": "950s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 6, - "y": 11, - "w": 28, - "h": 17 - }, - "frame": { - "x": 172, - "y": 148, - "w": 28, - "h": 17 - } - }, - { - "filename": "985", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 7, - "w": 27, - "h": 21 - }, - "frame": { - "x": 140, - "y": 165, - "w": 27, - "h": 21 - } - }, - { - "filename": "985s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 7, - "w": 27, - "h": 21 - }, - "frame": { - "x": 167, - "y": 165, - "w": 27, - "h": 21 - } - }, - { - "filename": "953", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 12, - "w": 24, - "h": 16 - }, - "frame": { - "x": 138, - "y": 186, - "w": 24, - "h": 16 - } - }, - { - "filename": "1010", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 2, - "w": 21, - "h": 26 - }, - "frame": { - "x": 142, - "y": 202, - "w": 21, - "h": 26 - } - }, - { - "filename": "953s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 12, - "w": 24, - "h": 16 - }, - "frame": { - "x": 162, - "y": 186, - "w": 24, - "h": 16 - } - }, - { - "filename": "1010s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 2, - "w": 21, - "h": 26 - }, - "frame": { - "x": 163, - "y": 202, - "w": 21, - "h": 26 - } - }, - { - "filename": "1002", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 6, - "y": 7, - "w": 27, - "h": 21 - }, - "frame": { - "x": 145, - "y": 228, - "w": 27, - "h": 21 - } - }, - { - "filename": "1017-cornerstone-mask", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 148, - "y": 249, - "w": 24, - "h": 24 - } - }, - { - "filename": "1017-cornerstone-mask-tera", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 148, - "y": 249, - "w": 24, - "h": 24 - } - }, - { - "filename": "1017s-cornerstone-mask", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 148, - "y": 249, - "w": 24, - "h": 24 - } - }, - { - "filename": "1017s-cornerstone-mask-tera", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 148, - "y": 249, - "w": 24, - "h": 24 - } - }, - { - "filename": "973", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 2, - "w": 15, - "h": 26 - }, - "frame": { - "x": 172, - "y": 228, - "w": 15, - "h": 26 - } - }, - { - "filename": "916s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 9, - "w": 25, - "h": 19 - }, - "frame": { - "x": 148, - "y": 273, - "w": 25, - "h": 19 - } - }, - { - "filename": "997", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 9, - "w": 21, - "h": 19 - }, - "frame": { - "x": 147, - "y": 292, - "w": 21, - "h": 19 + "x": 173, + "y": 147, + "w": 17, + "h": 23 } }, { @@ -3006,428 +2523,8 @@ "h": 25 }, "frame": { - "x": 148, - "y": 311, - "w": 20, - "h": 25 - } - }, - { - "filename": "910", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 7, - "w": 21, - "h": 21 - }, - "frame": { - "x": 148, - "y": 336, - "w": 21, - "h": 21 - } - }, - { - "filename": "906", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 9, - "w": 18, - "h": 19 - }, - "frame": { - "x": 172, - "y": 254, - "w": 18, - "h": 19 - } - }, - { - "filename": "906s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 9, - "w": 18, - "h": 19 - }, - "frame": { - "x": 173, - "y": 273, - "w": 18, - "h": 19 - } - }, - { - "filename": "1017-teal-mask", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 168, - "y": 292, - "w": 24, - "h": 24 - } - }, - { - "filename": "1017-teal-mask-tera", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 168, - "y": 292, - "w": 24, - "h": 24 - } - }, - { - "filename": "1017s-teal-mask", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 168, - "y": 292, - "w": 24, - "h": 24 - } - }, - { - "filename": "1017s-teal-mask-tera", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 168, - "y": 292, - "w": 24, - "h": 24 - } - }, - { - "filename": "943", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 24, - "h": 20 - }, - "frame": { - "x": 168, - "y": 316, - "w": 24, - "h": 20 - } - }, - { - "filename": "916s-female", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 6, - "y": 7, - "w": 25, - "h": 21 - }, - "frame": { - "x": 169, - "y": 336, - "w": 25, - "h": 21 - } - }, - { - "filename": "1002s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 6, - "y": 7, - "w": 27, - "h": 21 - }, - "frame": { - "x": 200, - "y": 152, - "w": 27, - "h": 21 - } - }, - { - "filename": "945", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 6, - "w": 25, - "h": 22 - }, - "frame": { - "x": 227, - "y": 171, - "w": 25, - "h": 22 - } - }, - { - "filename": "1012s-artisan", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 6, - "w": 26, - "h": 22 - }, - "frame": { - "x": 194, - "y": 173, - "w": 26, - "h": 22 - } - }, - { - "filename": "1012s-counterfeit", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 6, - "w": 26, - "h": 22 - }, - "frame": { - "x": 194, - "y": 173, - "w": 26, - "h": 22 - } - }, - { - "filename": "973s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 2, - "w": 15, - "h": 26 - }, - "frame": { - "x": 184, - "y": 202, - "w": 15, - "h": 26 - } - }, - { - "filename": "918", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 4, - "w": 22, - "h": 24 - }, - "frame": { - "x": 199, - "y": 195, - "w": 22, - "h": 24 - } - }, - { - "filename": "1017-wellspring-mask", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 221, - "y": 193, - "w": 24, - "h": 24 - } - }, - { - "filename": "1017-wellspring-mask-tera", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 221, - "y": 193, - "w": 24, - "h": 24 - } - }, - { - "filename": "1017s-wellspring-mask", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 221, - "y": 193, - "w": 24, - "h": 24 - } - }, - { - "filename": "1017s-wellspring-mask-tera", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 24, - "h": 24 - }, - "frame": { - "x": 221, - "y": 193, - "w": 24, - "h": 24 - } - }, - { - "filename": "949s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 3, - "w": 20, - "h": 25 - }, - "frame": { - "x": 187, - "y": 228, + "x": 177, + "y": 122, "w": 20, "h": 25 } @@ -3447,138 +2544,12 @@ "h": 20 }, "frame": { - "x": 190, - "y": 253, + "x": 178, + "y": 102, "w": 20, "h": 20 } }, - { - "filename": "967", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 9, - "w": 20, - "h": 19 - }, - "frame": { - "x": 191, - "y": 273, - "w": 20, - "h": 19 - } - }, - { - "filename": "962", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 3, - "w": 19, - "h": 25 - }, - "frame": { - "x": 192, - "y": 292, - "w": 19, - "h": 25 - } - }, - { - "filename": "967s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 9, - "w": 20, - "h": 19 - }, - "frame": { - "x": 192, - "y": 317, - "w": 20, - "h": 19 - } - }, - { - "filename": "910s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 7, - "w": 21, - "h": 21 - }, - "frame": { - "x": 194, - "y": 336, - "w": 21, - "h": 21 - } - }, - { - "filename": "962s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 3, - "w": 19, - "h": 25 - }, - "frame": { - "x": 207, - "y": 219, - "w": 19, - "h": 25 - } - }, - { - "filename": "918s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 4, - "w": 22, - "h": 24 - }, - "frame": { - "x": 226, - "y": 217, - "w": 22, - "h": 24 - } - }, { "filename": "923", "rotated": false, @@ -3594,77 +2565,14 @@ "h": 24 }, "frame": { - "x": 210, - "y": 244, + "x": 198, + "y": 101, "w": 19, "h": 24 } }, { - "filename": "961", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 5, - "w": 23, - "h": 23 - }, - "frame": { - "x": 229, - "y": 241, - "w": 23, - "h": 23 - } - }, - { - "filename": "961s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 5, - "w": 23, - "h": 23 - }, - "frame": { - "x": 229, - "y": 264, - "w": 23, - "h": 23 - } - }, - { - "filename": "909", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 9, - "w": 18, - "h": 19 - }, - "frame": { - "x": 211, - "y": 268, - "w": 18, - "h": 19 - } - }, - { - "filename": "945s", + "filename": "1012-artisan", "rotated": false, "trimmed": true, "sourceSize": { @@ -3674,16 +2582,58 @@ "spriteSourceSize": { "x": 7, "y": 6, - "w": 25, + "w": 26, "h": 22 }, "frame": { - "x": 211, - "y": 287, - "w": 25, + "x": 217, + "y": 102, + "w": 26, "h": 22 } }, + { + "filename": "908s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 2, + "w": 20, + "h": 26 + }, + "frame": { + "x": 197, + "y": 125, + "w": 20, + "h": 26 + } + }, + { + "filename": "1000s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 22, + "h": 27 + }, + "frame": { + "x": 217, + "y": 124, + "w": 22, + "h": 27 + } + }, { "filename": "948", "rotated": false, @@ -3699,8 +2649,8 @@ "h": 21 }, "frame": { - "x": 236, - "y": 287, + "x": 239, + "y": 124, "w": 16, "h": 21 } @@ -3720,14 +2670,35 @@ "h": 21 }, "frame": { - "x": 236, - "y": 308, + "x": 239, + "y": 145, "w": 16, "h": 21 } }, { - "filename": "927", + "filename": "1012-counterfeit", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 6, + "w": 26, + "h": 22 + }, + "frame": { + "x": 190, + "y": 151, + "w": 26, + "h": 22 + } + }, + { + "filename": "947s", "rotated": false, "trimmed": true, "sourceSize": { @@ -3736,19 +2707,82 @@ }, "spriteSourceSize": { "x": 8, - "y": 7, - "w": 24, - "h": 21 + "y": 6, + "w": 23, + "h": 22 }, "frame": { - "x": 212, - "y": 309, - "w": 24, - "h": 21 + "x": 216, + "y": 151, + "w": 23, + "h": 22 } }, { - "filename": "920", + "filename": "951", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 8, + "w": 16, + "h": 20 + }, + "frame": { + "x": 239, + "y": 166, + "w": 16, + "h": 20 + } + }, + { + "filename": "1017s-hearthflame-mask-tera", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 4, + "w": 25, + "h": 24 + }, + "frame": { + "x": 184, + "y": 173, + "w": 25, + "h": 24 + } + }, + { + "filename": "1017s-hearthflame-mask", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 4, + "w": 25, + "h": 24 + }, + "frame": { + "x": 209, + "y": 173, + "w": 25, + "h": 24 + } + }, + { + "filename": "1010", "rotated": false, "trimmed": true, "sourceSize": { @@ -3757,19 +2791,19 @@ }, "spriteSourceSize": { "x": 9, - "y": 5, - "w": 22, - "h": 23 + "y": 2, + "w": 21, + "h": 26 }, "frame": { - "x": 215, - "y": 330, - "w": 22, - "h": 23 + "x": 234, + "y": 186, + "w": 21, + "h": 26 } }, { - "filename": "912", + "filename": "936s", "rotated": false, "trimmed": true, "sourceSize": { @@ -3777,20 +2811,20 @@ "h": 30 }, "spriteSourceSize": { - "x": 13, - "y": 9, - "w": 15, - "h": 19 + "x": 10, + "y": 0, + "w": 20, + "h": 28 }, "frame": { - "x": 237, - "y": 329, - "w": 15, - "h": 19 + "x": 156, + "y": 213, + "w": 20, + "h": 28 } }, { - "filename": "912s", + "filename": "963", "rotated": false, "trimmed": true, "sourceSize": { @@ -3798,16 +2832,79 @@ "h": 30 }, "spriteSourceSize": { - "x": 13, - "y": 9, - "w": 15, - "h": 19 + "x": 9, + "y": 13, + "w": 22, + "h": 15 }, "frame": { - "x": 237, - "y": 348, - "w": 15, - "h": 19 + "x": 164, + "y": 198, + "w": 22, + "h": 15 + } + }, + { + "filename": "918", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 4, + "w": 22, + "h": 24 + }, + "frame": { + "x": 156, + "y": 241, + "w": 22, + "h": 24 + } + }, + { + "filename": "1006", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 22, + "h": 27 + }, + "frame": { + "x": 157, + "y": 265, + "w": 22, + "h": 27 + } + }, + { + "filename": "1012s-artisan", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 6, + "w": 26, + "h": 22 + }, + "frame": { + "x": 186, + "y": 197, + "w": 26, + "h": 22 } }, { @@ -3825,14 +2922,14 @@ "h": 22 }, "frame": { - "x": 215, - "y": 353, + "x": 212, + "y": 197, "w": 22, "h": 22 } }, { - "filename": "960", + "filename": "1010s", "rotated": false, "trimmed": true, "sourceSize": { @@ -3840,16 +2937,268 @@ "h": 30 }, "spriteSourceSize": { - "x": 12, - "y": 10, - "w": 15, - "h": 18 + "x": 9, + "y": 2, + "w": 21, + "h": 26 }, "frame": { - "x": 237, - "y": 367, - "w": 15, - "h": 18 + "x": 234, + "y": 212, + "w": 21, + "h": 26 + } + }, + { + "filename": "1012s-counterfeit", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 6, + "w": 26, + "h": 22 + }, + "frame": { + "x": 176, + "y": 219, + "w": 26, + "h": 22 + } + }, + { + "filename": "987", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 178, + "y": 241, + "w": 24, + "h": 24 + } + }, + { + "filename": "1006s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 22, + "h": 27 + }, + "frame": { + "x": 202, + "y": 219, + "w": 22, + "h": 27 + } + }, + { + "filename": "987s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 179, + "y": 265, + "w": 24, + "h": 24 + } + }, + { + "filename": "997", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 9, + "w": 21, + "h": 19 + }, + "frame": { + "x": 202, + "y": 246, + "w": 21, + "h": 19 + } + }, + { + "filename": "918s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 4, + "w": 22, + "h": 24 + }, + "frame": { + "x": 203, + "y": 265, + "w": 22, + "h": 24 + } + }, + { + "filename": "916s-female", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 6, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 157, + "y": 292, + "w": 25, + "h": 21 + } + }, + { + "filename": "1017-cornerstone-mask-tera", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 182, + "y": 289, + "w": 24, + "h": 24 + } + }, + { + "filename": "923s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 4, + "w": 19, + "h": 24 + }, + "frame": { + "x": 206, + "y": 289, + "w": 19, + "h": 24 + } + }, + { + "filename": "1023", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 20, + "h": 28 + }, + "frame": { + "x": 142, + "y": 329, + "w": 20, + "h": 28 + } + }, + { + "filename": "1023s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 1, + "w": 20, + "h": 28 + }, + "frame": { + "x": 143, + "y": 357, + "w": 20, + "h": 28 + } + }, + { + "filename": "953", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 12, + "w": 24, + "h": 16 + }, + "frame": { + "x": 150, + "y": 313, + "w": 24, + "h": 16 } }, { @@ -3867,8 +3216,8 @@ "h": 21 }, "frame": { - "x": 122, - "y": 355, + "x": 174, + "y": 313, "w": 25, "h": 21 } @@ -3888,12 +3237,264 @@ "h": 21 }, "frame": { - "x": 147, - "y": 357, + "x": 199, + "y": 313, "w": 25, "h": 21 } }, + { + "filename": "945", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 6, + "w": 25, + "h": 22 + }, + "frame": { + "x": 162, + "y": 334, + "w": 25, + "h": 22 + } + }, + { + "filename": "945s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 6, + "w": 25, + "h": 22 + }, + "frame": { + "x": 187, + "y": 334, + "w": 25, + "h": 22 + } + }, + { + "filename": "1017-cornerstone-mask", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 163, + "y": 356, + "w": 24, + "h": 24 + } + }, + { + "filename": "1017-teal-mask-tera", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 187, + "y": 356, + "w": 24, + "h": 24 + } + }, + { + "filename": "907", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 6, + "w": 17, + "h": 22 + }, + "frame": { + "x": 212, + "y": 334, + "w": 17, + "h": 22 + } + }, + { + "filename": "949s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 3, + "w": 20, + "h": 25 + }, + "frame": { + "x": 211, + "y": 356, + "w": 20, + "h": 25 + } + }, + { + "filename": "1017-teal-mask", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 231, + "y": 238, + "w": 24, + "h": 24 + } + }, + { + "filename": "1017-wellspring-mask-tera", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 231, + "y": 262, + "w": 24, + "h": 24 + } + }, + { + "filename": "1017-wellspring-mask", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 231, + "y": 286, + "w": 24, + "h": 24 + } + }, + { + "filename": "1017s-cornerstone-mask-tera", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 231, + "y": 310, + "w": 24, + "h": 24 + } + }, + { + "filename": "1017s-cornerstone-mask", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 231, + "y": 334, + "w": 24, + "h": 24 + } + }, + { + "filename": "1017s-teal-mask-tera", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 231, + "y": 358, + "w": 24, + "h": 24 + } + }, { "filename": "952", "rotated": false, @@ -3909,182 +3510,14 @@ "h": 22 }, "frame": { - "x": 172, - "y": 357, + "x": 163, + "y": 380, "w": 25, "h": 22 } }, { - "filename": "909s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 9, - "w": 18, - "h": 19 - }, - "frame": { - "x": 197, - "y": 357, - "w": 18, - "h": 19 - } - }, - { - "filename": "920s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 5, - "w": 22, - "h": 23 - }, - "frame": { - "x": 100, - "y": 359, - "w": 22, - "h": 23 - } - }, - { - "filename": "952s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 6, - "w": 25, - "h": 22 - }, - "frame": { - "x": 122, - "y": 376, - "w": 25, - "h": 22 - } - }, - { - "filename": "966", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 25, - "h": 21 - }, - "frame": { - "x": 147, - "y": 378, - "w": 25, - "h": 21 - } - }, - { - "filename": "966s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 25, - "h": 21 - }, - "frame": { - "x": 172, - "y": 379, - "w": 25, - "h": 21 - } - }, - { - "filename": "923s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 4, - "w": 19, - "h": 24 - }, - "frame": { - "x": 197, - "y": 376, - "w": 19, - "h": 24 - } - }, - { - "filename": "941", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 7, - "w": 21, - "h": 21 - }, - "frame": { - "x": 216, - "y": 375, - "w": 21, - "h": 21 - } - }, - { - "filename": "960s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 10, - "w": 15, - "h": 18 - }, - "frame": { - "x": 237, - "y": 385, - "w": 15, - "h": 18 - } - }, - { - "filename": "927s", + "filename": "961", "rotated": false, "trimmed": true, "sourceSize": { @@ -4093,122 +3526,17 @@ }, "spriteSourceSize": { "x": 8, - "y": 7, - "w": 24, - "h": 21 - }, - "frame": { - "x": 98, - "y": 382, - "w": 24, - "h": 21 - } - }, - { - "filename": "943s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 24, - "h": 20 - }, - "frame": { - "x": 74, - "y": 385, - "w": 24, - "h": 20 - } - }, - { - "filename": "986", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, "y": 5, "w": 23, "h": 23 }, "frame": { - "x": 74, - "y": 405, + "x": 188, + "y": 380, "w": 23, "h": 23 } }, - { - "filename": "986s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 5, - "w": 23, - "h": 23 - }, - "frame": { - "x": 74, - "y": 428, - "w": 23, - "h": 23 - } - }, - { - "filename": "947", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 6, - "w": 23, - "h": 22 - }, - "frame": { - "x": 74, - "y": 451, - "w": 23, - "h": 22 - } - }, - { - "filename": "947s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 8, - "y": 6, - "w": 23, - "h": 22 - }, - "frame": { - "x": 74, - "y": 473, - "w": 23, - "h": 22 - } - }, { "filename": "991", "rotated": false, @@ -4224,12 +3552,453 @@ "h": 24 }, "frame": { - "x": 77, - "y": 495, + "x": 211, + "y": 381, "w": 20, "h": 24 } }, + { + "filename": "1017s-teal-mask", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 231, + "y": 382, + "w": 24, + "h": 24 + } + }, + { + "filename": "952s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 6, + "w": 25, + "h": 22 + }, + "frame": { + "x": 52, + "y": 382, + "w": 25, + "h": 22 + } + }, + { + "filename": "1017s-wellspring-mask-tera", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 50, + "y": 404, + "w": 24, + "h": 24 + } + }, + { + "filename": "920", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 5, + "w": 22, + "h": 23 + }, + "frame": { + "x": 77, + "y": 382, + "w": 22, + "h": 23 + } + }, + { + "filename": "920s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 5, + "w": 22, + "h": 23 + }, + "frame": { + "x": 99, + "y": 383, + "w": 22, + "h": 23 + } + }, + { + "filename": "966-caph-starmobile", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 121, + "y": 385, + "w": 25, + "h": 21 + } + }, + { + "filename": "907s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 6, + "w": 17, + "h": 22 + }, + "frame": { + "x": 146, + "y": 385, + "w": 17, + "h": 22 + } + }, + { + "filename": "966-navi-starmobile", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 74, + "y": 405, + "w": 25, + "h": 21 + } + }, + { + "filename": "966-ruchbah-starmobile", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 99, + "y": 406, + "w": 25, + "h": 21 + } + }, + { + "filename": "910", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 21, + "h": 21 + }, + "frame": { + "x": 124, + "y": 406, + "w": 21, + "h": 21 + } + }, + { + "filename": "953s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 12, + "w": 24, + "h": 16 + }, + "frame": { + "x": 50, + "y": 428, + "w": 24, + "h": 16 + } + }, + { + "filename": "966-schedar-starmobile", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 74, + "y": 426, + "w": 25, + "h": 21 + } + }, + { + "filename": "966-segin-starmobile", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 99, + "y": 427, + "w": 25, + "h": 21 + } + }, + { + "filename": "910s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 21, + "h": 21 + }, + "frame": { + "x": 124, + "y": 427, + "w": 21, + "h": 21 + } + }, + { + "filename": "962", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 3, + "w": 19, + "h": 25 + }, + "frame": { + "x": 145, + "y": 407, + "w": 19, + "h": 25 + } + }, + { + "filename": "1017s-wellspring-mask", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 4, + "w": 24, + "h": 24 + }, + "frame": { + "x": 164, + "y": 402, + "w": 24, + "h": 24 + } + }, + { + "filename": "961s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 5, + "w": 23, + "h": 23 + }, + "frame": { + "x": 188, + "y": 403, + "w": 23, + "h": 23 + } + }, + { + "filename": "939", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 20, + "h": 21 + }, + "frame": { + "x": 211, + "y": 405, + "w": 20, + "h": 21 + } + }, + { + "filename": "927", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 7, + "w": 24, + "h": 21 + }, + "frame": { + "x": 231, + "y": 406, + "w": 24, + "h": 21 + } + }, + { + "filename": "962s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 3, + "w": 19, + "h": 25 + }, + "frame": { + "x": 52, + "y": 444, + "w": 19, + "h": 25 + } + }, + { + "filename": "966", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 71, + "y": 447, + "w": 25, + "h": 21 + } + }, { "filename": "991s", "rotated": false, @@ -4245,12 +4014,117 @@ "h": 24 }, "frame": { - "x": 78, - "y": 519, + "x": 52, + "y": 469, "w": 20, "h": 24 } }, + { + "filename": "986", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 5, + "w": 23, + "h": 23 + }, + "frame": { + "x": 72, + "y": 468, + "w": 23, + "h": 23 + } + }, + { + "filename": "966s-caph-starmobile", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 96, + "y": 448, + "w": 25, + "h": 21 + } + }, + { + "filename": "927s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 7, + "w": 24, + "h": 21 + }, + "frame": { + "x": 121, + "y": 448, + "w": 24, + "h": 21 + } + }, + { + "filename": "966s-navi-starmobile", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 95, + "y": 469, + "w": 25, + "h": 21 + } + }, + { + "filename": "966s-ruchbah-starmobile", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 120, + "y": 469, + "w": 25, + "h": 21 + } + }, { "filename": "1013-masterpiece", "rotated": false, @@ -4266,8 +4140,8 @@ "h": 24 }, "frame": { - "x": 78, - "y": 543, + "x": 145, + "y": 432, "w": 20, "h": 24 } @@ -4287,14 +4161,14 @@ "h": 24 }, "frame": { - "x": 78, - "y": 543, + "x": 145, + "y": 456, "w": 20, "h": 24 } }, { - "filename": "1013s-masterpiece", + "filename": "966s-schedar-starmobile", "rotated": false, "trimmed": true, "sourceSize": { @@ -4302,62 +4176,20 @@ "h": 30 }, "spriteSourceSize": { - "x": 10, - "y": 4, - "w": 20, - "h": 24 - }, - "frame": { - "x": 78, - "y": 567, - "w": 20, - "h": 24 - } - }, - { - "filename": "1013s-unremarkable", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 4, - "w": 20, - "h": 24 - }, - "frame": { - "x": 78, - "y": 567, - "w": 20, - "h": 24 - } - }, - { - "filename": "941s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, + "x": 7, "y": 7, - "w": 21, + "w": 25, "h": 21 }, "frame": { - "x": 216, - "y": 396, - "w": 21, + "x": 165, + "y": 426, + "w": 25, "h": 21 } }, { - "filename": "996", + "filename": "966s-segin-starmobile", "rotated": false, "trimmed": true, "sourceSize": { @@ -4365,16 +4197,37 @@ "h": 30 }, "spriteSourceSize": { - "x": 12, - "y": 12, - "w": 15, - "h": 16 + "x": 7, + "y": 7, + "w": 25, + "h": 21 }, "frame": { - "x": 237, - "y": 403, - "w": 15, - "h": 16 + "x": 165, + "y": 447, + "w": 25, + "h": 21 + } + }, + { + "filename": "966s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 25, + "h": 21 + }, + "frame": { + "x": 190, + "y": 426, + "w": 25, + "h": 21 } }, { @@ -4392,12 +4245,33 @@ "h": 21 }, "frame": { - "x": 122, - "y": 398, + "x": 190, + "y": 447, "w": 25, "h": 21 } }, + { + "filename": "973", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 2, + "w": 15, + "h": 26 + }, + "frame": { + "x": 215, + "y": 426, + "w": 15, + "h": 26 + } + }, { "filename": "8128s", "rotated": false, @@ -4413,14 +4287,14 @@ "h": 21 }, "frame": { - "x": 147, - "y": 399, + "x": 230, + "y": 427, "w": 25, "h": 21 } }, { - "filename": "1024", + "filename": "943", "rotated": false, "trimmed": true, "sourceSize": { @@ -4434,8 +4308,29 @@ "h": 20 }, "frame": { - "x": 172, - "y": 400, + "x": 165, + "y": 468, + "w": 24, + "h": 20 + } + }, + { + "filename": "943s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 24, + "h": 20 + }, + "frame": { + "x": 189, + "y": 468, "w": 24, "h": 20 } @@ -4455,12 +4350,33 @@ "h": 20 }, "frame": { - "x": 196, - "y": 400, + "x": 145, + "y": 480, "w": 20, "h": 20 } }, + { + "filename": "1024", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 24, + "h": 20 + }, + "frame": { + "x": 165, + "y": 488, + "w": 24, + "h": 20 + } + }, { "filename": "1024s", "rotated": false, @@ -4476,476 +4392,14 @@ "h": 20 }, "frame": { - "x": 98, - "y": 403, + "x": 189, + "y": 488, "w": 24, "h": 20 } }, { - "filename": "939", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 7, - "w": 20, - "h": 21 - }, - "frame": { - "x": 97, - "y": 423, - "w": 20, - "h": 21 - } - }, - { - "filename": "939s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 7, - "w": 20, - "h": 21 - }, - "frame": { - "x": 97, - "y": 444, - "w": 20, - "h": 21 - } - }, - { - "filename": "971", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 7, - "w": 20, - "h": 21 - }, - "frame": { - "x": 97, - "y": 465, - "w": 20, - "h": 21 - } - }, - { - "filename": "971s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 7, - "w": 20, - "h": 21 - }, - "frame": { - "x": 97, - "y": 486, - "w": 20, - "h": 21 - } - }, - { - "filename": "969", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 16, - "w": 18, - "h": 12 - }, - "frame": { - "x": 97, - "y": 507, - "w": 18, - "h": 12 - } - }, - { - "filename": "907", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 6, - "w": 17, - "h": 22 - }, - "frame": { - "x": 98, - "y": 519, - "w": 17, - "h": 22 - } - }, - { - "filename": "907s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 6, - "w": 17, - "h": 22 - }, - "frame": { - "x": 98, - "y": 541, - "w": 17, - "h": 22 - } - }, - { - "filename": "929", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 6, - "w": 17, - "h": 22 - }, - "frame": { - "x": 98, - "y": 563, - "w": 17, - "h": 22 - } - }, - { - "filename": "929s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 6, - "w": 17, - "h": 22 - }, - "frame": { - "x": 117, - "y": 423, - "w": 17, - "h": 22 - } - }, - { - "filename": "1011", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 5, - "w": 19, - "h": 22 - }, - "frame": { - "x": 117, - "y": 445, - "w": 19, - "h": 22 - } - }, - { - "filename": "1011s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 5, - "w": 19, - "h": 22 - }, - "frame": { - "x": 117, - "y": 467, - "w": 19, - "h": 22 - } - }, - { - "filename": "931-white-plumage", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 8, - "w": 20, - "h": 20 - }, - "frame": { - "x": 117, - "y": 489, - "w": 20, - "h": 20 - } - }, - { - "filename": "1004", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 8, - "w": 21, - "h": 20 - }, - "frame": { - "x": 115, - "y": 509, - "w": 21, - "h": 20 - } - }, - { - "filename": "1004s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 8, - "w": 21, - "h": 20 - }, - "frame": { - "x": 115, - "y": 529, - "w": 21, - "h": 20 - } - }, - { - "filename": "931-yellow-plumage", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 8, - "w": 20, - "h": 20 - }, - "frame": { - "x": 115, - "y": 549, - "w": 20, - "h": 20 - } - }, - { - "filename": "931s-blue-plumage", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 8, - "w": 20, - "h": 20 - }, - "frame": { - "x": 115, - "y": 569, - "w": 20, - "h": 20 - } - }, - { - "filename": "935s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 13, - "y": 7, - "w": 13, - "h": 21 - }, - "frame": { - "x": 134, - "y": 419, - "w": 13, - "h": 21 - } - }, - { - "filename": "931s-green-plumage", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 8, - "w": 20, - "h": 20 - }, - "frame": { - "x": 147, - "y": 420, - "w": 20, - "h": 20 - } - }, - { - "filename": "931s-white-plumage", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 8, - "w": 20, - "h": 20 - }, - "frame": { - "x": 167, - "y": 420, - "w": 20, - "h": 20 - } - }, - { - "filename": "931s-yellow-plumage", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 8, - "w": 20, - "h": 20 - }, - "frame": { - "x": 187, - "y": 420, - "w": 20, - "h": 20 - } - }, - { - "filename": "997s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 9, - "w": 21, - "h": 19 - }, - "frame": { - "x": 136, - "y": 440, - "w": 21, - "h": 19 - } - }, - { - "filename": "1015", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 7, - "w": 17, - "h": 21 - }, - "frame": { - "x": 136, - "y": 459, - "w": 17, - "h": 21 - } - }, - { - "filename": "932", + "filename": "906", "rotated": false, "trimmed": true, "sourceSize": { @@ -4959,222 +4413,12 @@ "h": 19 }, "frame": { - "x": 157, - "y": 440, + "x": 215, + "y": 452, "w": 18, "h": 19 } }, - { - "filename": "1015s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 7, - "w": 17, - "h": 21 - }, - "frame": { - "x": 153, - "y": 459, - "w": 17, - "h": 21 - } - }, - { - "filename": "932s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 9, - "w": 18, - "h": 19 - }, - "frame": { - "x": 175, - "y": 440, - "w": 18, - "h": 19 - } - }, - { - "filename": "958", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 8, - "w": 17, - "h": 20 - }, - "frame": { - "x": 170, - "y": 459, - "w": 17, - "h": 20 - } - }, - { - "filename": "922", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 9, - "w": 17, - "h": 19 - }, - "frame": { - "x": 193, - "y": 440, - "w": 17, - "h": 19 - } - }, - { - "filename": "958s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 8, - "w": 17, - "h": 20 - }, - "frame": { - "x": 187, - "y": 459, - "w": 17, - "h": 20 - } - }, - { - "filename": "951", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 8, - "w": 16, - "h": 20 - }, - "frame": { - "x": 207, - "y": 420, - "w": 16, - "h": 20 - } - }, - { - "filename": "922s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 9, - "w": 17, - "h": 19 - }, - "frame": { - "x": 210, - "y": 440, - "w": 17, - "h": 19 - } - }, - { - "filename": "963", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 9, - "y": 13, - "w": 22, - "h": 15 - }, - "frame": { - "x": 204, - "y": 459, - "w": 22, - "h": 15 - } - }, - { - "filename": "999-roaming", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 13, - "y": 10, - "w": 14, - "h": 18 - }, - "frame": { - "x": 223, - "y": 417, - "w": 14, - "h": 18 - } - }, - { - "filename": "996s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 12, - "w": 15, - "h": 16 - }, - "frame": { - "x": 237, - "y": 419, - "w": 15, - "h": 16 - } - }, { "filename": "963s", "rotated": false, @@ -5190,8 +4434,8 @@ "h": 15 }, "frame": { - "x": 227, - "y": 435, + "x": 233, + "y": 448, "w": 22, "h": 15 } @@ -5211,12 +4455,33 @@ "h": 15 }, "frame": { - "x": 227, - "y": 450, + "x": 233, + "y": 463, "w": 22, "h": 15 } }, + { + "filename": "1013s-masterpiece", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 4, + "w": 20, + "h": 24 + }, + "frame": { + "x": 213, + "y": 471, + "w": 20, + "h": 24 + } + }, { "filename": "964s-zero", "rotated": false, @@ -5232,8 +4497,8 @@ "h": 15 }, "frame": { - "x": 226, - "y": 465, + "x": 233, + "y": 478, "w": 22, "h": 15 } @@ -5253,12 +4518,33 @@ "h": 15 }, "frame": { - "x": 204, - "y": 474, + "x": 233, + "y": 493, "w": 22, "h": 15 } }, + { + "filename": "931-white-plumage", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 20, + "h": 20 + }, + "frame": { + "x": 213, + "y": 495, + "w": 20, + "h": 20 + } + }, { "filename": "974s", "rotated": false, @@ -5274,12 +4560,390 @@ "h": 15 }, "frame": { - "x": 226, - "y": 480, + "x": 233, + "y": 508, "w": 22, "h": 15 } }, + { + "filename": "1013s-unremarkable", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 4, + "w": 20, + "h": 24 + }, + "frame": { + "x": 54, + "y": 493, + "w": 20, + "h": 24 + } + }, + { + "filename": "986s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 5, + "w": 23, + "h": 23 + }, + "frame": { + "x": 54, + "y": 517, + "w": 23, + "h": 23 + } + }, + { + "filename": "941", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 21, + "h": 21 + }, + "frame": { + "x": 54, + "y": 540, + "w": 21, + "h": 21 + } + }, + { + "filename": "973s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 2, + "w": 15, + "h": 26 + }, + "frame": { + "x": 74, + "y": 491, + "w": 15, + "h": 26 + } + }, + { + "filename": "939s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 20, + "h": 21 + }, + "frame": { + "x": 55, + "y": 561, + "w": 20, + "h": 21 + } + }, + { + "filename": "913s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 5, + "w": 17, + "h": 23 + }, + "frame": { + "x": 77, + "y": 517, + "w": 17, + "h": 23 + } + }, + { + "filename": "1011", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 5, + "w": 19, + "h": 22 + }, + "frame": { + "x": 75, + "y": 540, + "w": 19, + "h": 22 + } + }, + { + "filename": "931-yellow-plumage", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 20, + "h": 20 + }, + "frame": { + "x": 75, + "y": 562, + "w": 20, + "h": 20 + } + }, + { + "filename": "941s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 21, + "h": 21 + }, + "frame": { + "x": 56, + "y": 582, + "w": 21, + "h": 21 + } + }, + { + "filename": "971", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 20, + "h": 21 + }, + "frame": { + "x": 56, + "y": 603, + "w": 20, + "h": 21 + } + }, + { + "filename": "1011s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 5, + "w": 19, + "h": 22 + }, + "frame": { + "x": 56, + "y": 624, + "w": 19, + "h": 22 + } + }, + { + "filename": "929", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 6, + "w": 17, + "h": 22 + }, + "frame": { + "x": 75, + "y": 624, + "w": 17, + "h": 22 + } + }, + { + "filename": "971s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 20, + "h": 21 + }, + "frame": { + "x": 77, + "y": 582, + "w": 20, + "h": 21 + } + }, + { + "filename": "1015", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 17, + "h": 21 + }, + "frame": { + "x": 76, + "y": 603, + "w": 17, + "h": 21 + } + }, + { + "filename": "929s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 6, + "w": 17, + "h": 22 + }, + "frame": { + "x": 92, + "y": 624, + "w": 17, + "h": 22 + } + }, + { + "filename": "1015s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 17, + "h": 21 + }, + "frame": { + "x": 93, + "y": 603, + "w": 17, + "h": 21 + } + }, + { + "filename": "935", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 13, + "h": 21 + }, + "frame": { + "x": 89, + "y": 491, + "w": 13, + "h": 21 + } + }, + { + "filename": "1004", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 8, + "w": 21, + "h": 20 + }, + "frame": { + "x": 102, + "y": 490, + "w": 21, + "h": 20 + } + }, { "filename": "980", "rotated": false, @@ -5295,12 +4959,33 @@ "h": 15 }, "frame": { - "x": 137, - "y": 480, + "x": 123, + "y": 490, "w": 22, "h": 15 } }, + { + "filename": "931s-blue-plumage", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 20, + "h": 20 + }, + "frame": { + "x": 145, + "y": 500, + "w": 20, + "h": 20 + } + }, { "filename": "980s", "rotated": false, @@ -5316,12 +5001,285 @@ "h": 15 }, "frame": { - "x": 137, - "y": 495, + "x": 123, + "y": 505, "w": 22, "h": 15 } }, + { + "filename": "997s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 9, + "w": 21, + "h": 19 + }, + "frame": { + "x": 165, + "y": 508, + "w": 21, + "h": 19 + } + }, + { + "filename": "1004s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 8, + "w": 21, + "h": 20 + }, + "frame": { + "x": 186, + "y": 508, + "w": 21, + "h": 20 + } + }, + { + "filename": "931s-green-plumage", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 20, + "h": 20 + }, + "frame": { + "x": 207, + "y": 515, + "w": 20, + "h": 20 + } + }, + { + "filename": "931s-white-plumage", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 20, + "h": 20 + }, + "frame": { + "x": 227, + "y": 523, + "w": 20, + "h": 20 + } + }, + { + "filename": "935s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 13, + "h": 21 + }, + "frame": { + "x": 94, + "y": 512, + "w": 13, + "h": 21 + } + }, + { + "filename": "951s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 8, + "w": 16, + "h": 20 + }, + "frame": { + "x": 107, + "y": 510, + "w": 16, + "h": 20 + } + }, + { + "filename": "931s-yellow-plumage", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 20, + "h": 20 + }, + "frame": { + "x": 94, + "y": 533, + "w": 20, + "h": 20 + } + }, + { + "filename": "967", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 9, + "w": 20, + "h": 19 + }, + "frame": { + "x": 123, + "y": 520, + "w": 20, + "h": 19 + } + }, + { + "filename": "967s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 9, + "w": 20, + "h": 19 + }, + "frame": { + "x": 143, + "y": 520, + "w": 20, + "h": 19 + } + }, + { + "filename": "970", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 12, + "w": 20, + "h": 16 + }, + "frame": { + "x": 163, + "y": 527, + "w": 20, + "h": 16 + } + }, + { + "filename": "970s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 12, + "w": 20, + "h": 16 + }, + "frame": { + "x": 183, + "y": 528, + "w": 20, + "h": 16 + } + }, + { + "filename": "906s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 9, + "w": 18, + "h": 19 + }, + "frame": { + "x": 95, + "y": 553, + "w": 18, + "h": 19 + } + }, + { + "filename": "958", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 8, + "w": 17, + "h": 20 + }, + "frame": { + "x": 97, + "y": 572, + "w": 17, + "h": 20 + } + }, { "filename": "915", "rotated": false, @@ -5337,8 +5295,8 @@ "h": 17 }, "frame": { - "x": 136, - "y": 510, + "x": 114, + "y": 539, "w": 19, "h": 17 } @@ -5358,96 +5316,12 @@ "h": 17 }, "frame": { - "x": 136, - "y": 527, + "x": 133, + "y": 539, "w": 19, "h": 17 } }, - { - "filename": "951s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 8, - "w": 16, - "h": 20 - }, - "frame": { - "x": 159, - "y": 480, - "w": 16, - "h": 20 - } - }, - { - "filename": "970", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 12, - "w": 20, - "h": 16 - }, - "frame": { - "x": 175, - "y": 479, - "w": 20, - "h": 16 - } - }, - { - "filename": "919", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 13, - "w": 16, - "h": 15 - }, - "frame": { - "x": 159, - "y": 500, - "w": 16, - "h": 15 - } - }, - { - "filename": "970s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 12, - "w": 20, - "h": 16 - }, - "frame": { - "x": 175, - "y": 495, - "w": 20, - "h": 16 - } - }, { "filename": "946", "rotated": false, @@ -5463,12 +5337,96 @@ "h": 16 }, "frame": { - "x": 155, - "y": 515, + "x": 113, + "y": 556, "w": 19, "h": 16 } }, + { + "filename": "909", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 9, + "w": 18, + "h": 19 + }, + "frame": { + "x": 114, + "y": 572, + "w": 18, + "h": 19 + } + }, + { + "filename": "909s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 9, + "w": 18, + "h": 19 + }, + "frame": { + "x": 132, + "y": 556, + "w": 18, + "h": 19 + } + }, + { + "filename": "932", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 9, + "w": 18, + "h": 19 + }, + "frame": { + "x": 132, + "y": 575, + "w": 18, + "h": 19 + } + }, + { + "filename": "938", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 14, + "y": 12, + "w": 11, + "h": 16 + }, + "frame": { + "x": 152, + "y": 539, + "w": 11, + "h": 16 + } + }, { "filename": "942", "rotated": false, @@ -5484,8 +5442,8 @@ "h": 15 }, "frame": { - "x": 155, - "y": 531, + "x": 163, + "y": 543, "w": 19, "h": 15 } @@ -5505,14 +5463,14 @@ "h": 15 }, "frame": { - "x": 136, + "x": 182, "y": 544, "w": 19, "h": 15 } }, { - "filename": "946s", + "filename": "932s", "rotated": false, "trimmed": true, "sourceSize": { @@ -5520,58 +5478,16 @@ "h": 30 }, "spriteSourceSize": { - "x": 10, - "y": 12, - "w": 19, - "h": 16 + "x": 11, + "y": 9, + "w": 18, + "h": 19 }, "frame": { - "x": 135, - "y": 559, - "w": 19, - "h": 16 - } - }, - { - "filename": "965", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 12, - "w": 19, - "h": 16 - }, - "frame": { - "x": 135, - "y": 575, - "w": 19, - "h": 16 - } - }, - { - "filename": "965s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 10, - "y": 12, - "w": 19, - "h": 16 - }, - "frame": { - "x": 155, - "y": 546, - "w": 19, - "h": 16 + "x": 150, + "y": 558, + "w": 18, + "h": 19 } }, { @@ -5589,12 +5505,327 @@ "h": 17 }, "frame": { - "x": 154, - "y": 562, + "x": 150, + "y": 577, "w": 18, "h": 17 } }, + { + "filename": "999-roaming", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 13, + "y": 10, + "w": 14, + "h": 18 + }, + "frame": { + "x": 168, + "y": 558, + "w": 14, + "h": 18 + } + }, + { + "filename": "946s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 12, + "w": 19, + "h": 16 + }, + "frame": { + "x": 182, + "y": 559, + "w": 19, + "h": 16 + } + }, + { + "filename": "912", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 13, + "y": 9, + "w": 15, + "h": 19 + }, + "frame": { + "x": 168, + "y": 576, + "w": 15, + "h": 19 + } + }, + { + "filename": "958s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 8, + "w": 17, + "h": 20 + }, + "frame": { + "x": 183, + "y": 575, + "w": 17, + "h": 20 + } + }, + { + "filename": "965", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 12, + "w": 19, + "h": 16 + }, + "frame": { + "x": 203, + "y": 535, + "w": 19, + "h": 16 + } + }, + { + "filename": "922", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 9, + "w": 17, + "h": 19 + }, + "frame": { + "x": 201, + "y": 551, + "w": 17, + "h": 19 + } + }, + { + "filename": "912s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 13, + "y": 9, + "w": 15, + "h": 19 + }, + "frame": { + "x": 218, + "y": 551, + "w": 15, + "h": 19 + } + }, + { + "filename": "922s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 9, + "w": 17, + "h": 19 + }, + "frame": { + "x": 233, + "y": 543, + "w": 17, + "h": 19 + } + }, + { + "filename": "926", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 13, + "w": 17, + "h": 15 + }, + "frame": { + "x": 233, + "y": 562, + "w": 17, + "h": 15 + } + }, + { + "filename": "965s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 10, + "y": 12, + "w": 19, + "h": 16 + }, + "frame": { + "x": 201, + "y": 570, + "w": 19, + "h": 16 + } + }, + { + "filename": "928", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 13, + "y": 10, + "w": 13, + "h": 18 + }, + "frame": { + "x": 220, + "y": 570, + "w": 13, + "h": 18 + } + }, + { + "filename": "926s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 13, + "w": 17, + "h": 15 + }, + "frame": { + "x": 233, + "y": 577, + "w": 17, + "h": 15 + } + }, + { + "filename": "969", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 16, + "w": 18, + "h": 12 + }, + "frame": { + "x": 200, + "y": 586, + "w": 18, + "h": 12 + } + }, + { + "filename": "960", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 10, + "w": 15, + "h": 18 + }, + "frame": { + "x": 218, + "y": 588, + "w": 15, + "h": 18 + } + }, + { + "filename": "940", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 11, + "y": 14, + "w": 17, + "h": 14 + }, + "frame": { + "x": 233, + "y": 592, + "w": 17, + "h": 14 + } + }, { "filename": "969s", "rotated": false, @@ -5610,8 +5841,8 @@ "h": 12 }, "frame": { - "x": 154, - "y": 579, + "x": 114, + "y": 591, "w": 18, "h": 12 } @@ -5631,35 +5862,14 @@ "h": 17 }, "frame": { - "x": 195, - "y": 489, + "x": 110, + "y": 603, "w": 18, "h": 17 } }, { - "filename": "928", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 13, - "y": 10, - "w": 13, - "h": 18 - }, - "frame": { - "x": 213, - "y": 489, - "w": 13, - "h": 18 - } - }, - { - "filename": "926", + "filename": "940s", "rotated": false, "trimmed": true, "sourceSize": { @@ -5668,57 +5878,15 @@ }, "spriteSourceSize": { "x": 11, - "y": 13, + "y": 14, "w": 17, - "h": 15 + "h": 14 }, "frame": { - "x": 226, - "y": 495, + "x": 132, + "y": 594, "w": 17, - "h": 15 - } - }, - { - "filename": "926s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 13, - "w": 17, - "h": 15 - }, - "frame": { - "x": 195, - "y": 506, - "w": 17, - "h": 15 - } - }, - { - "filename": "999s-roaming", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 13, - "y": 10, - "w": 14, - "h": 18 - }, - "frame": { - "x": 212, - "y": 507, - "w": 14, - "h": 18 + "h": 14 } }, { @@ -5736,14 +5904,14 @@ "h": 16 }, "frame": { - "x": 226, - "y": 510, + "x": 149, + "y": 594, "w": 17, "h": 16 } }, { - "filename": "940", + "filename": "955s", "rotated": false, "trimmed": true, "sourceSize": { @@ -5752,15 +5920,15 @@ }, "spriteSourceSize": { "x": 11, - "y": 14, + "y": 12, "w": 17, - "h": 14 + "h": 16 }, "frame": { - "x": 175, - "y": 511, + "x": 166, + "y": 595, "w": 17, - "h": 14 + "h": 16 } }, { @@ -5778,12 +5946,54 @@ "h": 18 }, "frame": { - "x": 174, - "y": 525, + "x": 183, + "y": 595, "w": 16, "h": 18 } }, + { + "filename": "919", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 13, + "w": 16, + "h": 15 + }, + "frame": { + "x": 199, + "y": 598, + "w": 16, + "h": 15 + } + }, + { + "filename": "919s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 13, + "w": 16, + "h": 15 + }, + "frame": { + "x": 128, + "y": 608, + "w": 16, + "h": 15 + } + }, { "filename": "921s", "rotated": false, @@ -5799,12 +6009,54 @@ "h": 18 }, "frame": { - "x": 174, - "y": 543, + "x": 144, + "y": 610, "w": 16, "h": 18 } }, + { + "filename": "960s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 10, + "w": 15, + "h": 18 + }, + "frame": { + "x": 109, + "y": 628, + "w": 15, + "h": 18 + } + }, + { + "filename": "999s-roaming", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 13, + "y": 10, + "w": 14, + "h": 18 + }, + "frame": { + "x": 124, + "y": 628, + "w": 14, + "h": 18 + } + }, { "filename": "928s", "rotated": false, @@ -5820,8 +6072,8 @@ "h": 18 }, "frame": { - "x": 172, - "y": 562, + "x": 138, + "y": 628, "w": 13, "h": 18 } @@ -5841,8 +6093,8 @@ "h": 17 }, "frame": { - "x": 185, - "y": 561, + "x": 160, + "y": 611, "w": 16, "h": 17 } @@ -5862,138 +6114,12 @@ "h": 17 }, "frame": { - "x": 190, - "y": 525, + "x": 151, + "y": 628, "w": 16, "h": 17 } }, - { - "filename": "955s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 12, - "w": 17, - "h": 16 - }, - "frame": { - "x": 190, - "y": 542, - "w": 17, - "h": 16 - } - }, - { - "filename": "919s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 13, - "w": 16, - "h": 15 - }, - "frame": { - "x": 206, - "y": 525, - "w": 16, - "h": 15 - } - }, - { - "filename": "944", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 13, - "w": 16, - "h": 15 - }, - "frame": { - "x": 207, - "y": 540, - "w": 16, - "h": 15 - } - }, - { - "filename": "940s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 11, - "y": 14, - "w": 17, - "h": 14 - }, - "frame": { - "x": 222, - "y": 526, - "w": 17, - "h": 14 - } - }, - { - "filename": "944s", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 12, - "y": 13, - "w": 16, - "h": 15 - }, - "frame": { - "x": 223, - "y": 540, - "w": 16, - "h": 15 - } - }, - { - "filename": "938", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 40, - "h": 30 - }, - "spriteSourceSize": { - "x": 14, - "y": 12, - "w": 11, - "h": 16 - }, - "frame": { - "x": 239, - "y": 526, - "w": 11, - "h": 16 - } - }, { "filename": "938s", "rotated": false, @@ -6009,12 +6135,96 @@ "h": 16 }, "frame": { - "x": 239, - "y": 542, + "x": 167, + "y": 628, "w": 11, "h": 16 } }, + { + "filename": "944", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 13, + "w": 16, + "h": 15 + }, + "frame": { + "x": 176, + "y": 613, + "w": 16, + "h": 15 + } + }, + { + "filename": "944s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 13, + "w": 16, + "h": 15 + }, + "frame": { + "x": 192, + "y": 613, + "w": 16, + "h": 15 + } + }, + { + "filename": "996", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 12, + "w": 15, + "h": 16 + }, + "frame": { + "x": 178, + "y": 628, + "w": 15, + "h": 16 + } + }, + { + "filename": "996s", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 12, + "y": 12, + "w": 15, + "h": 16 + }, + "frame": { + "x": 193, + "y": 628, + "w": 15, + "h": 16 + } + }, { "filename": "978-curly", "rotated": false, @@ -6030,8 +6240,8 @@ "h": 14 }, "frame": { - "x": 207, - "y": 555, + "x": 208, + "y": 613, "w": 15, "h": 14 } @@ -6051,8 +6261,8 @@ "h": 14 }, "frame": { - "x": 222, - "y": 555, + "x": 208, + "y": 627, "w": 15, "h": 14 } @@ -6072,8 +6282,8 @@ "h": 14 }, "frame": { - "x": 237, - "y": 558, + "x": 223, + "y": 606, "w": 15, "h": 14 } @@ -6093,8 +6303,8 @@ "h": 14 }, "frame": { - "x": 201, - "y": 569, + "x": 223, + "y": 620, "w": 15, "h": 14 } @@ -6114,8 +6324,8 @@ "h": 14 }, "frame": { - "x": 216, - "y": 569, + "x": 238, + "y": 606, "w": 15, "h": 14 } @@ -6135,8 +6345,8 @@ "h": 14 }, "frame": { - "x": 231, - "y": 572, + "x": 238, + "y": 620, "w": 15, "h": 14 } @@ -6147,6 +6357,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:d412b44b05c0ac7988fc321b8a4eb571:51dd93a83920102d7a1b879808f62790:6fb417eff82c0971c86b4818772ba292$" + "smartupdate": "$TexturePacker:SmartUpdate:b180859bc4c006d56ee5322ca73fa54e:212d282258f5086ad99b3b2b95f7ec1a:6fb417eff82c0971c86b4818772ba292$" } } diff --git a/public/images/pokemon_icons_9.png b/public/images/pokemon_icons_9.png index 6123a15cbe9..2985fb800d6 100644 Binary files a/public/images/pokemon_icons_9.png and b/public/images/pokemon_icons_9.png differ diff --git a/public/images/trainer/atticus.json b/public/images/trainer/atticus.json new file mode 100644 index 00000000000..95621998bf2 --- /dev/null +++ b/public/images/trainer/atticus.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "atticus.png", + "format": "RGBA8888", + "size": { + "w": 46, + "h": 46 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 21, + "y": 33, + "w": 43, + "h": 46 + }, + "frame": { + "x": 0, + "y": 0, + "w": 43, + "h": 46 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:6dcd7c3d3982793cbca0d6fcd1f9260e:19c44634629fadd9d039d23dc71ec987:d26ede35f15aa571d5a7a2dd2fb868e1$" + } +} diff --git a/public/images/trainer/atticus.png b/public/images/trainer/atticus.png new file mode 100644 index 00000000000..e3e7e870f2b Binary files /dev/null and b/public/images/trainer/atticus.png differ diff --git a/public/images/trainer/eri.json b/public/images/trainer/eri.json new file mode 100644 index 00000000000..fd4daf60437 --- /dev/null +++ b/public/images/trainer/eri.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "eri.png", + "format": "RGBA8888", + "size": { + "w": 74, + "h": 74 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 15, + "y": 5, + "w": 45, + "h": 74 + }, + "frame": { + "x": 0, + "y": 0, + "w": 45, + "h": 74 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:59594ac27e74ec85e2949d12ff680dc2:d65b6b00858ac47b26ef8393a8fa6795:d7f4cd3ff755f8074c14d3006b0c8301$" + } +} diff --git a/public/images/trainer/eri.png b/public/images/trainer/eri.png new file mode 100644 index 00000000000..0c9bdf7b47b Binary files /dev/null and b/public/images/trainer/eri.png differ diff --git a/public/images/trainer/expert_pokemon_breeder.json b/public/images/trainer/expert_pokemon_breeder.json new file mode 100644 index 00000000000..cd6ecffb267 --- /dev/null +++ b/public/images/trainer/expert_pokemon_breeder.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "expert_pokemon_breeder.png", + "format": "RGBA8888", + "size": { + "w": 39, + "h": 75 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 21, + "y": 3, + "w": 39, + "h": 75 + }, + "frame": { + "x": 0, + "y": 0, + "w": 39, + "h": 75 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:cb681265d8dca038a518ab14076fd140:18ff41b1ef6967682643a11695926e58:c59ea3971195f5a395b75223a77d9068$" + } +} diff --git a/public/images/trainer/expert_pokemon_breeder.png b/public/images/trainer/expert_pokemon_breeder.png new file mode 100644 index 00000000000..0625f5255c3 Binary files /dev/null and b/public/images/trainer/expert_pokemon_breeder.png differ diff --git a/public/images/trainer/giacomo.json b/public/images/trainer/giacomo.json new file mode 100644 index 00000000000..5eeb2cd685b --- /dev/null +++ b/public/images/trainer/giacomo.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "giacomo.png", + "format": "RGBA8888", + "size": { + "w": 75, + "h": 75 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 23, + "y": 4, + "w": 37, + "h": 75 + }, + "frame": { + "x": 0, + "y": 0, + "w": 37, + "h": 75 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:8c4e7da48e5667abc6d364330268c092:0fa43e58d8a746d3b86cb2dd763719f4:8603cc19e888c8c8de62177f4011577c$" + } +} diff --git a/public/images/trainer/giacomo.png b/public/images/trainer/giacomo.png new file mode 100644 index 00000000000..275f47fad3c Binary files /dev/null and b/public/images/trainer/giacomo.png differ diff --git a/public/images/trainer/mela.json b/public/images/trainer/mela.json new file mode 100644 index 00000000000..c9db18acc5a --- /dev/null +++ b/public/images/trainer/mela.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "mela.png", + "format": "RGBA8888", + "size": { + "w": 78, + "h": 78 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 18, + "y": 1, + "w": 46, + "h": 78 + }, + "frame": { + "x": 0, + "y": 0, + "w": 46, + "h": 78 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:e26d8c926c54c848cef673b3f59f35e7:ff040c2cebb1a92d2ef61dc91c018390:68668cf06383ff459cccaafb6bf56215$" + } +} diff --git a/public/images/trainer/mela.png b/public/images/trainer/mela.png new file mode 100644 index 00000000000..fbb08ed69cf Binary files /dev/null and b/public/images/trainer/mela.png differ diff --git a/public/images/trainer/ortega.json b/public/images/trainer/ortega.json new file mode 100644 index 00000000000..53bab5dba40 --- /dev/null +++ b/public/images/trainer/ortega.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "ortega.png", + "format": "RGBA8888", + "size": { + "w": 69, + "h": 69 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 8, + "y": 10, + "w": 53, + "h": 69 + }, + "frame": { + "x": 0, + "y": 0, + "w": 53, + "h": 69 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:c6ff92d90ed884222095de81d1db9166:a91cf3c83a063f549c52afb42f7ba3b0:c3f9fcec121c8bc93f2b230b20b79c57$" + } +} diff --git a/public/images/trainer/ortega.png b/public/images/trainer/ortega.png new file mode 100644 index 00000000000..7f694c6ded6 Binary files /dev/null and b/public/images/trainer/ortega.png differ diff --git a/public/images/trainer/penny.json b/public/images/trainer/penny.json new file mode 100644 index 00000000000..da64efffa3b --- /dev/null +++ b/public/images/trainer/penny.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "penny.png", + "format": "RGBA8888", + "size": { + "w": 75, + "h": 75 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 24, + "y": 4, + "w": 34, + "h": 75 + }, + "frame": { + "x": 0, + "y": 0, + "w": 34, + "h": 75 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:54f184bf1995a94a78aff33c9a851e6b:a6c9b3fe428b0cd0344b5cf14b999f36:cf221da9747cb8cb356053d3042d8d22$" + } +} diff --git a/public/images/trainer/penny.png b/public/images/trainer/penny.png new file mode 100644 index 00000000000..0e36760e21b Binary files /dev/null and b/public/images/trainer/penny.png differ diff --git a/public/images/trainer/star_grunt_f.json b/public/images/trainer/star_grunt_f.json new file mode 100644 index 00000000000..e26477e3512 --- /dev/null +++ b/public/images/trainer/star_grunt_f.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "star_grunt_f.png", + "format": "RGBA8888", + "size": { + "w": 68, + "h": 68 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 24, + "y": 11, + "w": 30, + "h": 68 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 68 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:b542a1bdd6995584fc776f75d578b434:f03fddece4494ab59698002fe6671972:c6f0e54e24ec5ffaa711700431b1955e$" + } +} diff --git a/public/images/trainer/star_grunt_f.png b/public/images/trainer/star_grunt_f.png new file mode 100644 index 00000000000..6eb63ae1e03 Binary files /dev/null and b/public/images/trainer/star_grunt_f.png differ diff --git a/public/images/trainer/star_grunt_m.json b/public/images/trainer/star_grunt_m.json new file mode 100644 index 00000000000..bf49e3027e6 --- /dev/null +++ b/public/images/trainer/star_grunt_m.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "star_grunt_m.png", + "format": "RGBA8888", + "size": { + "w": 70, + "h": 70 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 24, + "y": 9, + "w": 31, + "h": 70 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 70 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:abc4b0424c37fd55a2bf2e9f5142adce:41a140aa68a1eda61d9a00cab4e07721:a0796711f9e0333796b6629cd43ff8e8$" + } +} diff --git a/public/images/trainer/star_grunt_m.png b/public/images/trainer/star_grunt_m.png new file mode 100644 index 00000000000..a69359eda8e Binary files /dev/null and b/public/images/trainer/star_grunt_m.png differ diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 516662617f1..d1fcc00e692 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -782,6 +782,14 @@ export default class BattleScene extends SceneBase { return this.getPlayerField().find(p => p.isActive()); } + /** + * Finds the first {@linkcode Pokemon.isActive() | active PlayerPokemon} that isn't also currently switching out + * @returns Either the first {@linkcode PlayerPokemon} satisfying, or undefined if no player pokemon on the field satisfy + */ + getNonSwitchedPlayerPokemon(): PlayerPokemon | undefined { + return this.getPlayerField().find(p => p.isActive() && p.switchOutStatus === false); + } + /** * Returns an array of PlayerPokemon of length 1 or 2 depending on if double battles or not * @returns array of {@linkcode PlayerPokemon} @@ -799,6 +807,14 @@ export default class BattleScene extends SceneBase { return this.getEnemyField().find(p => p.isActive()); } + /** + * Finds the first {@linkcode Pokemon.isActive() | active EnemyPokemon} pokemon from the enemy that isn't also currently switching out + * @returns Either the first {@linkcode EnemyPokemon} satisfying, or undefined if no player pokemon on the field satisfy + */ + getNonSwitchedEnemyPokemon(): EnemyPokemon | undefined { + return this.getEnemyField().find(p => p.isActive() && p.switchOutStatus === false); + } + /** * Returns an array of EnemyPokemon of length 1 or 2 depending on if double battles or not * @returns array of {@linkcode EnemyPokemon} @@ -1911,6 +1927,19 @@ export default class BattleScene extends SceneBase { return false; } + /** + * Fades out current track for `delay` ms, then fades in new track. + * @param newBgmKey + * @param destroy + * @param delay + */ + fadeAndSwitchBgm(newBgmKey: string, destroy: boolean = false, delay: number = 2000) { + this.fadeOutBgm(delay, destroy); + this.time.delayedCall(delay, () => { + this.playBgm(newBgmKey); + }); + } + playSound(sound: string | AnySound, config?: object): AnySound { const key = typeof sound === "string" ? sound : sound.key; config = config ?? {}; @@ -2135,12 +2164,16 @@ export default class BattleScene extends SceneBase { return 20.87; case "battle_macro_grunt": // SWSH Trainer Battle return 11.56; + case "battle_star_grunt": //SV Team Star Battle + return 133.362; case "battle_galactic_admin": //BDSP Team Galactic Admin Battle return 11.997; case "battle_skull_admin": //SM Team Skull Admin Battle return 15.463; case "battle_oleana": //SWSH Oleana Battle return 14.110; + case "battle_star_admin": //SV Team Star Boss Battle + return 9.493; case "battle_rocket_boss": //USUM Giovanni Battle return 9.115; case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle @@ -2157,6 +2190,18 @@ export default class BattleScene extends SceneBase { return 13.13; case "battle_macro_boss": //SWSH Rose Battle return 11.42; + case "battle_star_boss": //SV Cassiopeia Battle + return 25.764; + case "mystery_encounter_gen_5_gts": // BW GTS + return 8.52; + case "mystery_encounter_gen_6_gts": // XY GTS + return 9.24; + case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff + return 4.78; + case "mystery_encounter_weird_dream": // EoS Temporal Spire + return 41.42; + case "mystery_encounter_delibirdy": // Firel Delibirdy + return 82.28; } return 0; @@ -2603,7 +2648,7 @@ export default class BattleScene extends SceneBase { } party.forEach((enemyPokemon: EnemyPokemon, i: integer) => { - if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i] && heldModifiersConfigs[i].length > 0) { + if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i]) { heldModifiersConfigs[i].forEach(mt => { let modifier: PokemonHeldItemModifier; if (mt.modifier instanceof PokemonHeldItemModifierType) { @@ -2614,8 +2659,7 @@ export default class BattleScene extends SceneBase { } const stackCount = mt.stackCount ?? 1; modifier.stackCount = stackCount; - // TODO: set isTransferable - // modifier.isTransferrable = mt.isTransferable ?? true; + modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable; this.addEnemyModifier(modifier, true); }); } else { diff --git a/src/battle.ts b/src/battle.ts index a886a0eb771..d99e1a91c15 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -14,8 +14,16 @@ import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; import i18next from "#app/plugins/i18n"; -import MysteryEncounter from "./data/mystery-encounters/mystery-encounter"; +import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; +import { CustomModifierSettings } from "#app/modifier/modifier-type"; +import { ModifierTier } from "#app/modifier/modifier-tier"; + +export enum ClassicFixedBossWaves { + // TODO: other fixed wave battles should be added here + EVIL_BOSS_1 = 115, + EVIL_BOSS_2 = 165, +} export enum BattleType { WILD, @@ -157,7 +165,7 @@ export default class Battle { } addPostBattleLoot(enemyPokemon: EnemyPokemon): void { - this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferrable, false).map(i => { + this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, false).map(i => { const ret = i as PokemonHeldItemModifier; //@ts-ignore - this is awful to fix/change ret.pokemonId = null; @@ -419,6 +427,7 @@ export class FixedBattleConfig { public getTrainer: GetTrainerFunc; public getEnemyParty: GetEnemyPartyFunc; public seedOffsetWaveIndex: number; + public customModifierRewardSettings?: CustomModifierSettings; setBattleType(battleType: BattleType): FixedBattleConfig { this.battleType = battleType; @@ -444,6 +453,11 @@ export class FixedBattleConfig { this.seedOffsetWaveIndex = seedOffsetWaveIndex; return this; } + + setCustomModifierRewards(customModifierRewardSettings: CustomModifierSettings) { + this.customModifierRewardSettings = customModifierRewardSettings; + return this; + } } @@ -503,29 +517,35 @@ export const classicFixedBattles: FixedBattleConfigs = { [8]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), [25]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_2, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), + .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_2, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) + .setCustomModifierRewards({ guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], allowLuckUpgrades: false }), [35]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT ], true)), + .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)), [55]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_3, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), + .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_3, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) + .setCustomModifierRewards({ guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], allowLuckUpgrades: false }), [62]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) - .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT ], true)), + .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)), [64]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) - .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT ], true)), + .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)), [66]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) - .setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.ROOD ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA ], true)), + .setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.ROOD ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA, [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ] ], true)), [95]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_4, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), + .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_4, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) + .setCustomModifierRewards({ guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA], allowLuckUpgrades: false }), [112]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) - .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT ], true)), + .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)), [114]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) - .setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.ROOD ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA ], true, 1)), - [115]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) - .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.MAXIE, TrainerType.ARCHIE, TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE, TrainerType.LUSAMINE, TrainerType.GUZMA, TrainerType.ROSE ])), + .setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.ROOD ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA, [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ] ], true, 1)), + [ClassicFixedBossWaves.EVIL_BOSS_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) + .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.MAXIE, TrainerType.ARCHIE, TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE, TrainerType.LUSAMINE, TrainerType.GUZMA, TrainerType.ROSE, TrainerType.PENNY ])) + .setCustomModifierRewards({ guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA], allowLuckUpgrades: false }), [145]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_5, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), - [165]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) - .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_2, TrainerType.MAXIE_2, TrainerType.ARCHIE_2, TrainerType.CYRUS_2, TrainerType.GHETSIS_2, TrainerType.LYSANDRE_2, TrainerType.LUSAMINE_2, TrainerType.GUZMA_2, TrainerType.ROSE_2 ])), + .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_5, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) + .setCustomModifierRewards({ guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA], allowLuckUpgrades: false }), + [ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) + .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_2, TrainerType.MAXIE_2, TrainerType.ARCHIE_2, TrainerType.CYRUS_2, TrainerType.GHETSIS_2, TrainerType.LYSANDRE_2, TrainerType.LUSAMINE_2, TrainerType.GUZMA_2, TrainerType.ROSE_2, TrainerType.PENNY_2 ])) + .setCustomModifierRewards({ guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA], allowLuckUpgrades: false }), [182]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, [ TrainerType.HALA, TrainerType.MOLAYNE ], TrainerType.MARNIE_ELITE, TrainerType.RIKA, TrainerType.CRISPIN ])), [184]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) @@ -538,4 +558,5 @@ export const classicFixedBattles: FixedBattleConfigs = { .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BLUE, [ TrainerType.RED, TrainerType.LANCE_CHAMPION ], [ TrainerType.STEVEN, TrainerType.WALLACE ], TrainerType.CYNTHIA, [ TrainerType.ALDER, TrainerType.IRIS ], TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, [ TrainerType.GEETA, TrainerType.NEMONA ], TrainerType.KIERAN ])), [195]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_6, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) + .setCustomModifierRewards({ guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], allowLuckUpgrades: false }) }; diff --git a/src/constants.ts b/src/constants.ts index a2f7e47b996..0b1261ad814 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1 +1,5 @@ -export const PLAYER_PARTY_MAX_SIZE = 6; +/** The maximum size of the player's party */ +export const PLAYER_PARTY_MAX_SIZE: number = 6; + +/** Whether to use seasonal splash messages in general */ +export const USE_SEASONAL_SPLASH_MESSAGES: boolean = false; diff --git a/src/data/ability.ts b/src/data/ability.ts index 0edbc172ad5..58233e9839e 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1670,7 +1670,7 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { return new Promise(resolve => { if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.stealCondition || this.stealCondition(pokemon, defender, move))) { - const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferrable); + const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => { @@ -1763,7 +1763,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { return new Promise(resolve => { if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { - const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferrable); + const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => { diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index d972e48df7c..d7b995f748f 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -428,7 +428,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent { moveAnim.bgSprite.setScale(1.25); moveAnim.bgSprite.setAlpha(this.opacity / 255); scene.field.add(moveAnim.bgSprite); - const fieldPokemon = scene.getEnemyPokemon() || scene.getPlayerPokemon(); + const fieldPokemon = scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon(); if (!isNullOrUndefined(priority)) { scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority!); } else if (fieldPokemon?.isOnField()) { @@ -989,7 +989,7 @@ export abstract class BattleAnim { const setSpritePriority = (priority: integer) => { switch (priority) { case 0: - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getEnemyPokemon() || scene.getPlayerPokemon()!); // TODO: is this bang correct? + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon()!); // This bang assumes that if (the EnemyPokemon is undefined, then the PlayerPokemon function must return an object), correct assumption? break; case 1: scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 2205519c532..46b56a30835 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -451,6 +451,30 @@ export class SingleGenerationChallenge extends Challenge { applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean { let trainerTypes: TrainerType[] = []; switch (waveIndex) { + case 35: + trainerTypes = [ TrainerType.ROCKET_GRUNT, TrainerType.ROCKET_GRUNT, Utils.randSeedItem([ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ]), TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, Utils.randSeedItem([ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ]), TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ]; + break; + case 62: + trainerTypes = [ TrainerType.ROCKET_GRUNT, TrainerType.ROCKET_GRUNT, Utils.randSeedItem([ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ]), TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, Utils.randSeedItem([ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ]), TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ]; + break; + case 64: + trainerTypes = [ TrainerType.ROCKET_GRUNT, TrainerType.ROCKET_GRUNT, Utils.randSeedItem([ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ]), TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, Utils.randSeedItem([ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ]), TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ]; + break; + case 66: + trainerTypes = [ Utils.randSeedItem([ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ]), Utils.randSeedItem([ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ]), Utils.randSeedItem([ TrainerType.TABITHA, TrainerType.COURTNEY, TrainerType.MATT, TrainerType.SHELLY ]), Utils.randSeedItem([ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ]), Utils.randSeedItem([ TrainerType.ZINZOLIN, TrainerType.ROOD ]), Utils.randSeedItem([ TrainerType.XEROSIC, TrainerType.BRYONY ]), Utils.randSeedItem([ TrainerType.FABA, TrainerType.PLUMERIA ]), TrainerType.OLEANA, Utils.randSeedItem([ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]) ]; + break; + case 112: + trainerTypes = [ TrainerType.ROCKET_GRUNT, TrainerType.ROCKET_GRUNT, Utils.randSeedItem([ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ]), TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, Utils.randSeedItem([ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ]), TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ]; + break; + case 114: + trainerTypes = [ Utils.randSeedItem([ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ]), Utils.randSeedItem([ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ]), Utils.randSeedItem([ TrainerType.TABITHA, TrainerType.COURTNEY, TrainerType.MATT, TrainerType.SHELLY ]), Utils.randSeedItem([ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ]), Utils.randSeedItem([ TrainerType.ZINZOLIN, TrainerType.ROOD ]), Utils.randSeedItem([ TrainerType.XEROSIC, TrainerType.BRYONY ]), Utils.randSeedItem([ TrainerType.FABA, TrainerType.PLUMERIA ]), TrainerType.OLEANA, Utils.randSeedItem([ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]) ]; + break; + case 115: + trainerTypes = [ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.ROCKET_BOSS_GIOVANNI_1, Utils.randSeedItem([ TrainerType.MAXIE, TrainerType.ARCHIE ]), TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE, Utils.randSeedItem([ TrainerType.LUSAMINE, TrainerType.GUZMA ]), TrainerType.ROSE, TrainerType.PENNY ]; + break; + case 165: + trainerTypes = [ TrainerType.ROCKET_BOSS_GIOVANNI_2, TrainerType.ROCKET_BOSS_GIOVANNI_2, Utils.randSeedItem([ TrainerType.MAXIE_2, TrainerType.ARCHIE_2 ]), TrainerType.CYRUS_2, TrainerType.GHETSIS_2, TrainerType.LYSANDRE_2, Utils.randSeedItem([ TrainerType.LUSAMINE_2, TrainerType.GUZMA_2 ]), TrainerType.ROSE_2, TrainerType.PENNY_2 ]; + break; case 182: trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; break; diff --git a/src/data/dialogue.ts b/src/data/dialogue.ts index b01242d083a..b39e45ce632 100644 --- a/src/data/dialogue.ts +++ b/src/data/dialogue.ts @@ -837,11 +837,15 @@ export const trainerTypeDialogue: TrainerTypeDialogue = { "dialogue:macro_grunt.encounter.1", "dialogue:macro_grunt.encounter.2", "dialogue:macro_grunt.encounter.3", + "dialogue:macro_grunt.encounter.4", + "dialogue:macro_grunt.encounter.5", ], victory: [ "dialogue:macro_grunt.victory.1", "dialogue:macro_grunt.victory.2", "dialogue:macro_grunt.victory.3", + "dialogue:macro_grunt.victory.4", + "dialogue:macro_grunt.victory.5", ] } ], @@ -859,6 +863,66 @@ export const trainerTypeDialogue: TrainerTypeDialogue = { ] } ], + [TrainerType.STAR_GRUNT]: [ + { + encounter: [ + "dialogue:star_grunt.encounter.1", + ], + victory: [ + "dialogue:star_grunt.victory.1", + ] + } + ], + [TrainerType.GIACOMO]: [ + { + encounter: [ + "dialogue:giacomo.encounter.1", + ], + victory: [ + "dialogue:giacomo.victory.1", + ] + } + ], + [TrainerType.MELA]: [ + { + encounter: [ + "dialogue:mela.encounter.1", + ], + victory: [ + "dialogue:mela.victory.1", + ] + } + ], + [TrainerType.ATTICUS]: [ + { + encounter: [ + "dialogue:atticus.encounter.1", + ], + victory: [ + "dialogue:atticus.victory.1", + ] + } + ], + [TrainerType.ORTEGA]: [ + { + encounter: [ + "dialogue:ortega.encounter.1", + ], + victory: [ + "dialogue:ortega.victory.1", + ] + } + ], + [TrainerType.ERI]: [ + { + encounter: [ + "dialogue:eri.encounter.1", + ], + victory: [ + "dialogue:eri.victory.1", + ] + } + ], [TrainerType.ROCKET_BOSS_GIOVANNI_1]: [ { encounter: [ @@ -1093,6 +1157,32 @@ export const trainerTypeDialogue: TrainerTypeDialogue = { ] } ], + [TrainerType.PENNY]: [ + { + encounter: [ + "dialogue:star_boss_penny_1.encounter.1" + ], + victory: [ + "dialogue:star_boss_penny_1.victory.1" + ], + defeat: [ + "dialogue:star_boss_penny_1.defeat.1" + ] + } + ], + [TrainerType.PENNY_2]: [ + { + encounter: [ + "dialogue:star_boss_penny_2.encounter.1" + ], + victory: [ + "dialogue:star_boss_penny_2.victory.1" + ], + defeat: [ + "dialogue:star_boss_penny_2.defeat.1" + ] + } + ], [TrainerType.BUCK]: [ { encounter: [ diff --git a/src/data/egg-moves.ts b/src/data/egg-moves.ts index b516238c46e..3e58f993df2 100644 --- a/src/data/egg-moves.ts +++ b/src/data/egg-moves.ts @@ -264,7 +264,7 @@ export const speciesEggMoves = { [Species.PANPOUR]: [ Moves.NASTY_PLOT, Moves.ENERGY_BALL, Moves.EARTH_POWER, Moves.STEAM_ERUPTION ], [Species.MUNNA]: [ Moves.COSMIC_POWER, Moves.AURA_SPHERE, Moves.EARTH_POWER, Moves.MYSTICAL_POWER ], [Species.PIDOVE]: [ Moves.GUNK_SHOT, Moves.TIDY_UP, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ], - [Species.BLITZLE]: [ Moves.HIGH_HORSEPOWER, Moves.THUNDEROUS_KICK, Moves.FLARE_BLITZ, Moves.VOLT_TACKLE ], + [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.IRON_HEAD, Moves.MOUNTAIN_GALE, Moves.SHIFT_GEAR, Moves.THOUSAND_ARROWS ], diff --git a/src/data/move.ts b/src/data/move.ts index 1bfe20abc48..de5176c3c84 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1,16 +1,15 @@ -import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; -import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, TrappedTag, SubstituteTag, TypeBoostTag } from "./battler-tags"; +import { ChargeAnim, initMoveAnim, loadMoveAnimAssets, MoveChargeAnim } from "./battle-anims"; +import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, SubstituteTag, TrappedTag, TypeBoostTag } from "./battler-tags"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon"; -import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects } from "./status-effect"; +import { getNonVolatileStatusEffects, getStatusEffectHealText, isNonVolatileStatusEffect, StatusEffect } from "./status-effect"; import { getTypeDamageMultiplier, Type } from "./type"; -import { Constructor } from "#app/utils"; +import { Constructor, NumberHolder } from "#app/utils"; import * as Utils from "../utils"; import { WeatherType } from "./weather"; import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag"; -import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr, IgnoreMoveEffectsAbAttr, applyPreDefendAbAttrs, MoveEffectChanceMultiplierAbAttr, WonderSkinAbAttr, applyPreAttackAbAttrs, MoveTypeChangeAbAttr, UserFieldMoveTypePowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr, AllyMoveCategoryPowerBoostAbAttr, VariableMovePowerAbAttr } from "./ability"; -import { allAbilities } from "./ability"; -import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier, PokemonMoveAccuracyBoosterModifier, AttackTypeBoosterModifier, PokemonMultiHitModifier } from "../modifier/modifier"; +import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability"; +import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier"; import { BattlerIndex, BattleType } from "../battle"; import { TerrainType } from "./terrain"; import { ModifierPoolType } from "#app/modifier/modifier-type"; @@ -25,7 +24,7 @@ import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { MoveUsedEvent } from "#app/events/battle-scene"; -import { Stat, type BattleStat, type EffectiveStat, BATTLE_STATS, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat"; +import { BATTLE_STATS, type BattleStat, EFFECTIVE_STATS, type EffectiveStat, getStatKey, Stat } from "#app/enums/stat"; import { PartyStatusCurePhase } from "#app/phases/party-status-cure-phase"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; @@ -36,7 +35,6 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { SwitchPhase } from "#app/phases/switch-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms"; -import { NumberHolder } from "#app/utils"; import { GameMode } from "#app/game-mode"; import { applyChallenges, ChallengeType } from "./challenge"; @@ -2136,7 +2134,7 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { if (rand >= this.chance) { return resolve(false); } - const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferrable); + const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable); if (heldItems.length) { const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct? @@ -2213,7 +2211,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { } // Considers entire transferrable item pool by default (Knock Off). Otherwise berries only if specified (Incinerate). - let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferrable); + let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable); if (this.berriesOnly) { heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer()); @@ -2417,6 +2415,16 @@ export class BypassSleepAttr extends MoveAttr { return false; } + + /** + * Returns arbitrarily high score when Pokemon is asleep, otherwise shouldn't be used + * @param user + * @param target + * @param move + */ + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + return user.status && user.status.effect === StatusEffect.SLEEP ? 200 : -10; + } } /** @@ -3974,18 +3982,17 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr { export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const category = (args[0] as Utils.NumberHolder); - const atkRatio = user.getEffectiveStat(Stat.ATK, target, move) / target.getEffectiveStat(Stat.DEF, user, move); - const specialRatio = user.getEffectiveStat(Stat.SPATK, target, move) / target.getEffectiveStat(Stat.SPDEF, user, move); - // Shell Side Arm is much more complicated than it looks, this is a partial implementation to try to achieve something similar to the games - if (atkRatio > specialRatio) { + const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true); + const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true); + + if (predictedPhysDmg > predictedSpecDmg) { category.value = MoveCategory.PHYSICAL; return true; - } else if (atkRatio === specialRatio && user.randSeedInt(2) === 0) { + } else if (predictedPhysDmg === predictedSpecDmg && user.randSeedInt(2) === 0) { category.value = MoveCategory.PHYSICAL; return true; } - return false; } } @@ -4852,16 +4859,18 @@ export class RemoveAllSubstitutesAttr extends MoveEffectAttr { } /** - * Attribute used when a move hits a {@linkcode BattlerTagType} for double damage + * Attribute used when a move can deal damage to {@linkcode BattlerTagType} + * Moves that always hit but do not deal double damage: Thunder, Fissure, Sky Uppercut, + * Smack Down, Hurricane, Thousand Arrows * @extends MoveAttr */ -export class DealsDoubleDamageToTagAttr extends MoveAttr { +export class HitsTagAttr extends MoveAttr { /** The {@linkcode BattlerTagType} this move hits */ public tagType: BattlerTagType; - /** Should this move deal double damage against {@linkcode DealsDoubleDamageToTagAttr.tagType}? */ + /** Should this move deal double damage against {@linkcode HitsTagAttr.tagType}? */ public doubleDamage: boolean; - constructor(tagType: BattlerTagType, doubleDamage?: boolean) { + constructor(tagType: BattlerTagType, doubleDamage: boolean = false) { super(); this.tagType = tagType; @@ -4873,6 +4882,17 @@ export class DealsDoubleDamageToTagAttr extends MoveAttr { } } +/** + * Used for moves that will always hit for a given tag but also doubles damage. + * Moves include: Gust, Stomp, Body Slam, Surf, Earthquake, Magnitude, Twister, + * Whirlpool, Dragon Rush, Heat Crash, Steam Roller, Flying Press + */ +export class HitsTagForDoubleDamageAttr extends HitsTagAttr { + constructor(tagType: BattlerTagType) { + super(tagType, true); + } +} + export class AddArenaTagAttr extends MoveEffectAttr { public tagType: ArenaTagType; public turnCount: integer; @@ -5201,7 +5221,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { switchOutTarget.leaveField(false); if (switchOutTarget.hp) { - switchOutTarget.setWildFlee(true); user.scene.queueMessage(i18next.t("moveTriggers:fled", {pokemonName: getPokemonNameWithAffix(switchOutTarget)}), null, true, 500); // in double battles redirect potential moves off fled pokemon @@ -6393,7 +6412,7 @@ export class AttackedByItemAttr extends MoveAttr { */ getCondition(): MoveConditionFunc { return (user: Pokemon, target: Pokemon, move: Move) => { - const heldItems = target.getHeldItems().filter(i => i.isTransferrable); + const heldItems = target.getHeldItems().filter(i => i.isTransferable); if (heldItems.length === 0) { return false; } @@ -6752,7 +6771,7 @@ export function initMoves() { new AttackMove(Moves.CUT, Type.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, 0, 1) .slicingMove(), new AttackMove(Moves.GUST, Type.FLYING, MoveCategory.SPECIAL, 40, 100, 35, -1, 0, 1) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, true) + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.FLYING) .windMove(), new AttackMove(Moves.WING_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, 0, 1), new StatusMove(Moves.WHIRLWIND, Type.NORMAL, -1, 20, -1, -6, 1) @@ -6770,7 +6789,7 @@ export function initMoves() { new AttackMove(Moves.VINE_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, 0, 1), new AttackMove(Moves.STOMP, Type.NORMAL, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 1) .attr(AlwaysHitMinimizeAttr) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED) .attr(FlinchAttr), new AttackMove(Moves.DOUBLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 30, 100, 30, -1, 0, 1) .attr(MultiHitAttr, MultiHitType._2), @@ -6795,7 +6814,7 @@ export function initMoves() { new AttackMove(Moves.TACKLE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1), new AttackMove(Moves.BODY_SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 30, 0, 1) .attr(AlwaysHitMinimizeAttr) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED) .attr(StatusEffectAttr, StatusEffect.PARALYSIS), new AttackMove(Moves.WRAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, 0, 1) .attr(TrapAttr, BattlerTagType.WRAP), @@ -6863,7 +6882,7 @@ export function initMoves() { new AttackMove(Moves.HYDRO_PUMP, Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, -1, 0, 1), new AttackMove(Moves.SURF, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 1) .target(MoveTarget.ALL_NEAR_OTHERS) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERWATER, true) + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERWATER) .attr(GulpMissileTagAttr), new AttackMove(Moves.ICE_BEAM, Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1) .attr(StatusEffectAttr, StatusEffect.FREEZE), @@ -6946,18 +6965,18 @@ export function initMoves() { new AttackMove(Moves.THUNDER, Type.ELECTRIC, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 1) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) .attr(ThunderAccuracyAttr) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false), + .attr(HitsTagAttr, BattlerTagType.FLYING), new AttackMove(Moves.ROCK_THROW, Type.ROCK, MoveCategory.PHYSICAL, 50, 90, 15, -1, 0, 1) .makesContact(false), new AttackMove(Moves.EARTHQUAKE, Type.GROUND, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 1) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, true) + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERGROUND) .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(Moves.FISSURE, Type.GROUND, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1) .attr(OneHitKOAttr) .attr(OneHitKOAccuracyAttr) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, false) + .attr(HitsTagAttr, BattlerTagType.UNDERGROUND) .makesContact(false), new AttackMove(Moves.DIG, Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1) .attr(ChargeAttr, ChargeAnim.DIG_CHARGING, i18next.t("moveTriggers:dugAHole", {pokemonName: "{USER}"}), BattlerTagType.UNDERGROUND) @@ -7346,7 +7365,7 @@ export function initMoves() { .attr(PreMoveMessageAttr, magnitudeMessageFunc) .attr(MagnitudePowerAttr) .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, true) + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERGROUND) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(Moves.DYNAMIC_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 50, 5, 100, 0, 2) @@ -7402,7 +7421,7 @@ export function initMoves() { new AttackMove(Moves.CROSS_CHOP, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 80, 5, -1, 0, 2) .attr(HighCritAttr), new AttackMove(Moves.TWISTER, Type.DRAGON, MoveCategory.SPECIAL, 40, 100, 20, 20, 0, 2) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, true) + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.FLYING) .attr(FlinchAttr) .windMove() .target(MoveTarget.ALL_NEAR_ENEMIES), @@ -7434,7 +7453,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.DEF ], -1), new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2) .attr(TrapAttr, BattlerTagType.WHIRLPOOL) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERWATER, true), + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERWATER), new AttackMove(Moves.BEAT_UP, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 2) .attr(MultiHitAttr, MultiHitType.BEAT_UP) .attr(BeatUpAttr) @@ -7532,7 +7551,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true) .condition((user, target, move) => !target.status && !target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)), new AttackMove(Moves.KNOCK_OFF, Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferrable).length > 0 ? 1.5 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1) .attr(RemoveHeldItemAttr, false), new AttackMove(Moves.ENDEAVOR, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 3) .attr(MatchHpAttr) @@ -7657,7 +7676,7 @@ export function initMoves() { new AttackMove(Moves.EXTRASENSORY, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, 10, 0, 3) .attr(FlinchAttr), new AttackMove(Moves.SKY_UPPERCUT, Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, 0, 3) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING) + .attr(HitsTagAttr, BattlerTagType.FLYING) .punchingMove(), new AttackMove(Moves.SAND_TOMB, Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 3) .attr(TrapAttr, BattlerTagType.SAND_TOMB) @@ -7889,7 +7908,7 @@ export function initMoves() { .pulseMove(), new AttackMove(Moves.DRAGON_RUSH, Type.DRAGON, MoveCategory.PHYSICAL, 100, 75, 10, 20, 0, 4) .attr(AlwaysHitMinimizeAttr) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED) .attr(FlinchAttr), new AttackMove(Moves.POWER_GEM, Type.ROCK, MoveCategory.SPECIAL, 80, 100, 20, -1, 0, 4), new AttackMove(Moves.DRAIN_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 4) @@ -8086,7 +8105,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN]) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false) + .attr(HitsTagAttr, BattlerTagType.FLYING) .makesContact(false), new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5) .attr(CritOnlyAttr), @@ -8101,7 +8120,7 @@ export function initMoves() { new AttackMove(Moves.HEAVY_SLAM, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5) .attr(AlwaysHitMinimizeAttr) .attr(CompareWeightPowerAttr) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true), + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED), new AttackMove(Moves.SYNCHRONOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 5) .target(MoveTarget.ALL_NEAR_OTHERS) .condition(unknownTypeCondition) @@ -8184,7 +8203,7 @@ export function initMoves() { new StatusMove(Moves.QUASH, Type.DARK, 100, 15, -1, 0, 5) .unimplemented(), new AttackMove(Moves.ACROBATICS, Type.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5) - .attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferrable).reduce((v, m) => v + m.stackCount, 0))), + .attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferable).reduce((v, m) => v + m.stackCount, 0))), new StatusMove(Moves.REFLECT_TYPE, Type.NORMAL, -1, 15, -1, 0, 5) .ignoresSubstitute() .attr(CopyTypeAttr), @@ -8254,12 +8273,12 @@ export function initMoves() { new AttackMove(Moves.HEAT_CRASH, Type.FIRE, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5) .attr(AlwaysHitMinimizeAttr) .attr(CompareWeightPowerAttr) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true), + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED), new AttackMove(Moves.LEAF_TORNADO, Type.GRASS, MoveCategory.SPECIAL, 65, 90, 10, 50, 0, 5) .attr(StatStageChangeAttr, [ Stat.ACC ], -1), new AttackMove(Moves.STEAMROLLER, Type.BUG, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 5) .attr(AlwaysHitMinimizeAttr) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED) .attr(FlinchAttr), new SelfStatusMove(Moves.COTTON_GUARD, Type.GRASS, -1, 10, -1, 0, 5) .attr(StatStageChangeAttr, [ Stat.DEF ], 3, true), @@ -8272,7 +8291,7 @@ export function initMoves() { new AttackMove(Moves.HURRICANE, Type.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 5) .attr(ThunderAccuracyAttr) .attr(ConfuseAttr) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false) + .attr(HitsTagAttr, BattlerTagType.FLYING) .windMove(), new AttackMove(Moves.HEAD_CHARGE, Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 5) .attr(RecoilAttr) @@ -8328,7 +8347,7 @@ export function initMoves() { new AttackMove(Moves.FLYING_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, 0, 6) .attr(AlwaysHitMinimizeAttr) .attr(FlyingTypeMultiplierAttr) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) + .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED) .condition(failOnGravityCondition), new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6) .target(MoveTarget.USER_SIDE) @@ -8499,8 +8518,8 @@ export function initMoves() { new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) .attr(NeutralDamageAgainstFlyingTypeMultiplierAttr) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MAGNET_RISEN, false) + .attr(HitsTagAttr, BattlerTagType.FLYING) + .attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN]) .makesContact(false) @@ -8758,7 +8777,7 @@ export function initMoves() { .ignoresVirtual(), new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7) .attr(AlwaysHitMinimizeAttr) - .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) + .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true) .partial() .ignoresVirtual(), new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) @@ -9106,7 +9125,7 @@ export function initMoves() { new AttackMove(Moves.SHELL_SIDE_ARM, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 20, 0, 8) .attr(ShellSideArmCategoryAttr) .attr(StatusEffectAttr, StatusEffect.POISON) - .partial(), + .partial(), // Physical version of the move does not make contact new AttackMove(Moves.MISTY_EXPLOSION, Type.FAIRY, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8) .attr(SacrificialAttr) .target(MoveTarget.ALL_NEAR_OTHERS) diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index b66ca10c9f5..f7666fa1b37 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -2,7 +2,7 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattl import { trainerConfigs, } from "#app/data/trainer-config"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { TrainerType } from "#enums/trainer-type"; import { Species } from "#enums/species"; @@ -99,7 +99,7 @@ export const ATrainersTestEncounter: MysteryEncounter = const trainerConfig = trainerConfigs[trainerType].clone(); const trainerSpriteKey = trainerConfig.getSpriteKey(); encounter.enemyPartyConfigs.push({ - levelAdditiveMultiplier: 1, + levelAdditiveModifier: 1, trainerConfig: trainerConfig }); @@ -152,7 +152,6 @@ export const ATrainersTestEncounter: MysteryEncounter = }; encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.epic`)); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.SACRED_ASH], guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA], fillRemaining: true }, [eggOptions]); - return initBattleWithEnemyConfig(scene, config); } ) @@ -180,7 +179,7 @@ export const ATrainersTestEncounter: MysteryEncounter = ) .withOutroDialogue([ { - text: `${namespace}.outro`, - }, + text: `${namespace}.outro` + } ]) .build(); diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index a9a273c6ec4..18f998192ce 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -4,9 +4,9 @@ import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "# import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; -import { PersistentModifierRequirement } from "../mystery-encounter-requirements"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -208,7 +208,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = // Calculate boss mon const config: EnemyPartyConfig = { - levelAdditiveMultiplier: 1, + levelAdditiveModifier: 1, pokemonConfigs: [ { species: getPokemonSpecies(Species.GREEDENT), @@ -291,7 +291,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; const berryMap = encounter.misc.berryItemsMap; - // Returns 2/5 of the berries stolen from each Pokemon + // Returns 2/5 of the berries stolen to each Pokemon const party = scene.getParty(); party.forEach(pokemon => { const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id); @@ -310,6 +310,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = } } }); + await scene.updateModifiers(true); transitionMysteryEncounterIntroVisuals(scene, true, true, 500); leaveEncounterWithoutBattle(scene, true); diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index 9f38b5a4dea..eb43424a8ff 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -3,9 +3,9 @@ import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; -import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "../mystery-encounter-requirements"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { getPokemonSpecies } from "#app/data/pokemon-species"; diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 7e6914cabdd..523b8598f95 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -17,13 +17,13 @@ import { randSeedInt } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getPokemonNameWithAffix } from "#app/messages"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { TrainerSlot } from "#app/data/trainer-config"; -import { applyModifierTypeToPlayerPokemon, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { applyModifierTypeToPlayerPokemon, getEncounterPokemonLevelForWave, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import PokemonData from "#app/system/pokemon-data"; import { BerryModifier } from "#app/modifier/modifier"; import i18next from "#app/plugins/i18n"; @@ -56,12 +56,11 @@ export const BerriesAboundEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; // Calculate boss mon - const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0); + const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true); const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); const config: EnemyPartyConfig = { - levelAdditiveMultiplier: 1, pokemonConfigs: [{ level: level, species: bossSpecies, @@ -163,10 +162,10 @@ export const BerriesAboundEncounter: MysteryEncounter = .withOptionPhase(async (scene: BattleScene) => { // Pick race for berries const encounter = scene.currentBattle.mysteryEncounter!; - const fastestPokemon = encounter.misc.fastestPokemon; - const enemySpeed = encounter.misc.enemySpeed; + const fastestPokemon: PlayerPokemon = encounter.misc.fastestPokemon; + const enemySpeed: number = encounter.misc.enemySpeed; const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1); - const numBerries = encounter.misc.numBerries; + const numBerries: number = encounter.misc.numBerries; const shopOptions: ModifierTypeOption[] = []; for (let i = 0; i < 5; i++) { diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index 7fdaec35dc3..202488030ee 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -9,6 +9,7 @@ import { transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { + getRandomPartyMemberFunc, trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, @@ -17,14 +18,12 @@ import { import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import BattleScene from "#app/battle-scene"; -import * as Utils from "#app/utils"; import { isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { TrainerType } from "#enums/trainer-type"; import { Species } from "#enums/species"; -import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { Moves } from "#enums/moves"; @@ -192,6 +191,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1), new TypeRequirement(Type.BUG, false, 1) )) + .withMaxAllowedEncounters(1) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withIntroSpriteConfigs([]) // These are set in onInit() .withAutoHideIntroVisuals(false) @@ -286,7 +286,8 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = // Player gets different rewards depending on the number of bug types they have const numBugTypes = scene.getParty().filter(p => p.isOfType(Type.BUG, true)).length; - encounter.setDialogueToken("numBugTypes", numBugTypes.toString()); + const numBugTypesText = i18next.t(`${namespace}.numBugTypes`, { count: numBugTypes }); + encounter.setDialogueToken("numBugTypes", numBugTypesText); if (numBugTypes < 2) { setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL], fillRemaining: false }); @@ -582,16 +583,6 @@ function getTrainerConfigForWave(waveIndex: number) { return config; } -function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: TrainerSlot = TrainerSlot.TRAINER, ignoreEvolution: boolean = false, postProcess?: (enemyPokemon: EnemyPokemon) => void) { - return (scene: BattleScene, level: number, strength: PartyMemberStrength) => { - let species = Utils.randSeedItem(speciesPool); - if (!ignoreEvolution) { - species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength); - } - return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, undefined, postProcess); - }; -} - function doBugTypeMoveTutor(scene: BattleScene): Promise { return new Promise(async resolve => { const moveOptions = scene.currentBattle.mysteryEncounter!.misc.moveTutorOptions; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 061d2a33e8a..d930e43c45f 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -5,7 +5,7 @@ import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifi import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; @@ -246,12 +246,12 @@ export const ClowningAroundEncounter: MysteryEncounter = const party = scene.getParty(); let mostHeldItemsPokemon = party[0]; let count = mostHeldItemsPokemon.getHeldItems() - .filter(m => m.isTransferrable && !(m instanceof BerryModifier)) + .filter(m => m.isTransferable && !(m instanceof BerryModifier)) .reduce((v, m) => v + m.stackCount, 0); party.forEach(pokemon => { const nextCount = pokemon.getHeldItems() - .filter(m => m.isTransferrable && !(m instanceof BerryModifier)) + .filter(m => m.isTransferable && !(m instanceof BerryModifier)) .reduce((v, m) => v + m.stackCount, 0); if (nextCount > count) { mostHeldItemsPokemon = pokemon; @@ -276,7 +276,7 @@ export const ClowningAroundEncounter: MysteryEncounter = // Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm) let numUltra = 0; let numRogue = 0; - items.filter(m => m.isTransferrable && !(m instanceof BerryModifier)) + items.filter(m => m.isTransferable && !(m instanceof BerryModifier)) .forEach(m => { const type = m.type.withTierFromPool(); const tier = type.tier ?? ModifierTier.ULTRA; diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 046e2b2f876..8a0a18d48ea 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -3,8 +3,8 @@ import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/po import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { getPokemonSpecies } from "#app/data/pokemon-species"; @@ -19,7 +19,7 @@ import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter- import { DANCING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { BattlerIndex } from "#app/battle"; -import { catchPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { catchPokemon, getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { PokeballType } from "#enums/pokeball"; import { modifierTypes } from "#app/modifier/modifier-type"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; @@ -107,7 +107,7 @@ export const DancingLessonsEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; const species = getPokemonSpecies(Species.ORICORIO); - const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0); + const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); const enemyPokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, false); if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) { if (enemyPokemon.moveset.length < 4) { @@ -133,7 +133,7 @@ export const DancingLessonsEncounter: MysteryEncounter = } const oricorioData = new PokemonData(enemyPokemon); - const oricorio = scene.addEnemyPokemon(species, scene.currentBattle.enemyLevels![0], TrainerSlot.NONE, false, oricorioData); + const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, oricorioData); // Adds a real Pokemon sprite to the field (required for the animation) scene.getEnemyParty().forEach(enemyPokemon => { @@ -146,7 +146,6 @@ export const DancingLessonsEncounter: MysteryEncounter = encounter.loadAssets.push(oricorio.loadAssets()); const config: EnemyPartyConfig = { - levelAdditiveMultiplier: 1, pokemonConfigs: [{ species: species, dataSource: oricorioData, diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 212ff6ed1bb..09b058ab7c9 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -5,8 +5,8 @@ import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; import { modifierTypes } from "#app/modifier/modifier-type"; import { getPokemonSpecies } from "#app/data/pokemon-species"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils"; import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -18,7 +18,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; /** i18n namespace for encounter */ const namespace = "mysteryEncounter:darkDeal"; -/** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, and egg-locked mythicals */ +/** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, and Mythicals */ const excludedBosses = [ Species.NECROZMA, Species.COSMOG, @@ -63,11 +63,24 @@ const excludedBosses = [ Species.CELEBI, Species.DEOXYS, Species.JIRACHI, + Species.DARKRAI, Species.PHIONE, Species.MANAPHY, Species.ARCEUS, + Species.SHAYMIN, Species.VICTINI, + Species.MELOETTA, + Species.KELDEO, + Species.GENESECT, + Species.DIANCIE, + Species.HOOPA, + Species.VOLCANION, + Species.MAGEARNA, + Species.MARSHADOW, + Species.ZERAORA, + Species.ZARUDE, Species.MELTAN, + Species.MELMETAL, Species.PECHARUNT, ]; @@ -151,7 +164,7 @@ export const DarkDealEncounter: MysteryEncounter = // Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+ const roll = randSeedInt(100); const starterTier: number | [number, number] = - roll > 65 ? 6 : roll > 15 ? 7 : roll > 5 ? 8 : [9, 10]; + roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [9, 10]; const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterTier(starterTier, excludedBosses, bossTypes)); const pokemonConfig: EnemyPokemonConfig = { species: bossSpecies, diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index ed9344d3c95..25959abe19e 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -4,9 +4,9 @@ import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifi import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; -import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "../mystery-encounter-requirements"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -33,6 +33,8 @@ const OPTION_3_DISALLOWED_MODIFIERS = [ "PokemonBaseStatTotalModifier" ]; +const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 1.5; + /** * Delibird-y encounter. * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3804 | GitHub Issue #3804} @@ -42,7 +44,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) - .withSceneRequirement(new MoneyRequirement(0, 2)) // Must have enough money for it to spawn at the very least + .withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least .withPrimaryPokemonRequirement(new CombinationPokemonRequirement( // Must also have either option 2 or 3 available to spawn new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS), new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true) @@ -93,12 +95,18 @@ export const DelibirdyEncounter: MysteryEncounter = .withOnInit((scene: BattleScene) => { const encounter = scene.currentBattle.mysteryEncounter!; encounter.setDialogueToken("delibirdName", getPokemonSpecies(Species.DELIBIRD).getName()); + + scene.loadBgm("mystery_encounter_delibirdy", "mystery_encounter_delibirdy.mp3"); + return true; + }) + .withOnVisualsStart((scene: BattleScene) => { + scene.fadeAndSwitchBgm("mystery_encounter_delibirdy"); return true; }) .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) - .withSceneMoneyRequirement(0, 2) // Must have money to spawn + .withSceneMoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER) // Must have money to spawn .withDialogue({ buttonLabel: `${namespace}.option.1.label`, buttonTooltip: `${namespace}.option.1.tooltip`, diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index e35ca08b6a0..104b46bce8a 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -9,7 +9,7 @@ import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; import MysteryEncounter, { MysteryEncounterBuilder, -} from "../mystery-encounter"; +} from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index e0101d60a2a..d03a3c1fcca 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -6,7 +6,7 @@ import { modifierTypes } from "#app/modifier/modifier-type"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { Stat } from "#enums/stat"; diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 1861abcc7a4..470c4b96c82 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -3,8 +3,8 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounte import { AttackTypeBoosterModifierType, modifierTypes, } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { TypeRequirement } from "../mystery-encounter-requirements"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { Species } from "#enums/species"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Gender } from "#app/data/gender"; diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index c163a2fc194..aa11a07f218 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -17,12 +17,12 @@ import { } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MoveRequirement } from "../mystery-encounter-requirements"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { TrainerSlot } from "#app/data/trainer-config"; -import { getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { getEncounterPokemonLevelForWave, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import PokemonData from "#app/system/pokemon-data"; import { BattlerTagType } from "#enums/battler-tag-type"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -54,12 +54,11 @@ export const FightOrFlightEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; // Calculate boss mon - const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0); + const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true); const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true); encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender()); const config: EnemyPartyConfig = { - levelAdditiveMultiplier: 1, pokemonConfigs: [{ level: level, species: bossSpecies, @@ -69,7 +68,7 @@ export const FightOrFlightEncounter: MysteryEncounter = mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`); // Randomly boost 1 stat 2 stages - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [randSeedInt(5)], 2)); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2)); } }], }; 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 a544657e47c..9ca7c7c2865 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -1,7 +1,7 @@ import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { TrainerSlot } from "#app/data/trainer-config"; import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon"; @@ -84,12 +84,7 @@ export const FunAndGamesEncounter: MysteryEncounter = return true; }) .withOnVisualsStart((scene: BattleScene) => { - // Change the bgm - scene.fadeOutBgm(2000, false); - scene.time.delayedCall(2000, () => { - scene.playBgm("mystery_encounter_fun_and_games"); - }); - + scene.fadeAndSwitchBgm("mystery_encounter_fun_and_games"); return true; }) .withOption(MysteryEncounterOptionBuilder @@ -175,7 +170,9 @@ async function summonPlayerPokemon(scene: BattleScene) { const party = scene.getParty(); const chosenIndex = party.indexOf(playerPokemon); if (chosenIndex !== 0) { - [party[chosenIndex], party[0]] = [party[chosenIndex], party[chosenIndex]]; + const leadPokemon = party[0]; + party[0] = playerPokemon; + party[chosenIndex] = leadPokemon; } // Do trainer summon animation diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 1c5a1f009d9..55d4953d438 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -4,7 +4,7 @@ import { ModifierTier } from "#app/modifier/modifier-tier"; import { getPlayerModifierTypeOptions, ModifierPoolType, ModifierTypeOption, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { Species } from "#enums/species"; import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; @@ -23,6 +23,7 @@ import { getPokeballAtlasKey, getPokeballTintColor, PokeballType } from "#app/da import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { trainerNamePools } from "#app/data/trainer-names"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounter:globalTradeSystem"; @@ -118,17 +119,13 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = return true; }) .withOnVisualsStart((scene: BattleScene) => { - // Change the bgm - scene.fadeOutBgm(1500, false); - scene.time.delayedCall(1500, () => { - scene.playBgm(scene.currentBattle.mysteryEncounter!.misc.bgmKey); - }); - + scene.fadeAndSwitchBgm(scene.currentBattle.mysteryEncounter!.misc.bgmKey); return true; }) .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) + .withHasDexProgress(true) .withDialogue({ buttonLabel: `${namespace}.option.1.label`, buttonTooltip: `${namespace}.option.1.tooltip`, @@ -200,6 +197,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon); await showEncounterText(scene, `${namespace}.trade_received`, null, 0, true, 4000); scene.playBgm(encounter.misc.bgmKey); + await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon); await hideTradeBackground(scene); tradedPokemon.destroy(); @@ -210,6 +208,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) + .withHasDexProgress(true) .withDialogue({ buttonLabel: `${namespace}.option.2.label`, buttonTooltip: `${namespace}.option.2.tooltip`, @@ -218,8 +217,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Randomly generate a Wonder Trade pokemon - // const randomTradeOption = generateTradeOption(scene.getParty().map(p => p.species)); - const randomTradeOption = getPokemonSpecies(Species.BURMY); + const randomTradeOption = generateTradeOption(scene.getParty().map(p => p.species)); const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false); // Extra shiny roll at 1/128 odds (boosted by events and charms) if (!tradePokemon.shiny) { @@ -280,7 +278,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = await showTradeBackground(scene); await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon); await showEncounterText(scene, `${namespace}.trade_received`, null, 0, true, 4000); - scene.playBgm(scene.currentBattle.mysteryEncounter!.misc.bgmKey); + scene.playBgm(encounter.misc.bgmKey); + await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon); await hideTradeBackground(scene); tradedPokemon.destroy(); @@ -301,7 +300,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones const validItems = pokemon.getHeldItems().filter((it) => { - return it.isTransferrable; + return it.isTransferable; }); return validItems.map((modifier: PokemonHeldItemModifier) => { @@ -322,7 +321,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = const selectableFilter = (pokemon: Pokemon) => { // If pokemon has items to trade const meetsReqs = pokemon.getHeldItems().filter((it) => { - return it.isTransferrable; + return it.isTransferable; }).length > 0; if (!meetsReqs) { return getEncounterText(scene, `${namespace}.option.3.invalid_selection`) ?? null; diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index 16568d8cb7d..509ffb11b26 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -3,8 +3,8 @@ import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils"; import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index 71a44bd6852..ac257a8975f 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -15,7 +15,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import BattleScene from "#app/battle-scene"; import * as Utils from "#app/utils"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; @@ -75,7 +75,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = const hardSpriteKey = hardConfig.getSpriteKey(female, hardConfig.doubleOnly); encounter.enemyPartyConfigs.push({ trainerConfig: hardConfig, - levelAdditiveMultiplier: 1, + levelAdditiveModifier: 1, female: female, }); @@ -98,7 +98,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = const brutalSpriteKey = brutalConfig.getSpriteKey(female, brutalConfig.doubleOnly); encounter.enemyPartyConfigs.push({ trainerConfig: brutalConfig, - levelAdditiveMultiplier: 1.5, + levelAdditiveModifier: 1.5, female: female, }); diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 26e846ed874..18b2db53ba2 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -5,8 +5,8 @@ import { ModifierTier } from "#app/modifier/modifier-tier"; import { randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { getPokemonSpecies } from "#app/data/pokemon-species"; @@ -68,7 +68,7 @@ export const MysteriousChestEncounter: MysteryEncounter = // Calculate boss mon const config: EnemyPartyConfig = { - levelAdditiveMultiplier: 0.5, + levelAdditiveModifier: 0.5, disableSwitch: true, pokemonConfigs: [ { @@ -179,6 +179,7 @@ export const MysteriousChestEncounter: MysteryEncounter = encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender()); await showEncounterText(scene, `${namespace}.option.1.bad`); transitionMysteryEncounterIntroVisuals(scene, true, true, 500); + setEncounterRewards(scene, { fillRemaining: true }); await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); } } diff --git a/src/data/mystery-encounters/encounters/part-timer-encounter.ts b/src/data/mystery-encounters/encounters/part-timer-encounter.ts index 2abbb53c333..f5486d34ea9 100644 --- a/src/data/mystery-encounters/encounters/part-timer-encounter.ts +++ b/src/data/mystery-encounters/encounters/part-timer-encounter.ts @@ -2,8 +2,8 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MoveRequirement } from "../mystery-encounter-requirements"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { Stat } from "#enums/stat"; diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 49abf98cf49..2690460757f 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -1,7 +1,7 @@ import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import MysteryEncounterOption, { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { TrainerSlot } from "#app/data/trainer-config"; import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier"; @@ -25,7 +25,7 @@ const namespace = "mysteryEncounter:safariZone"; const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768]; -const SAFARI_MONEY_MULTIPLIER = 2.75; +const SAFARI_MONEY_MULTIPLIER = 2; /** * Safari Zone encounter. diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 8ee4782def5..933f184351a 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -5,9 +5,9 @@ import { randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; -import { MoneyRequirement } from "../mystery-encounter-requirements"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -19,6 +19,9 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounter:shadyVitaminDealer"; +const VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER = 1.5; +const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 3.5; + /** * Shady Vitamin Dealer encounter. * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3798 | GitHub Issue #3798} @@ -28,7 +31,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) - .withSceneRequirement(new MoneyRequirement(0, 1.5)) // Must have the money for at least the cheap deal + .withSceneRequirement(new MoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)) // Must have the money for at least the cheap deal .withPrimaryPokemonHealthRatioRequirement([0.5, 1]) // At least 1 Pokemon must have above half HP .withIntroSpriteConfigs([ { @@ -64,7 +67,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) - .withSceneMoneyRequirement(0, 1.5) + .withSceneMoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER) .withDialogue({ buttonLabel: `${namespace}.option.1.label`, buttonTooltip: `${namespace}.option.1.tooltip`, @@ -115,7 +118,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType); } - leaveEncounterWithoutBattle(scene); + leaveEncounterWithoutBattle(scene, true); }) .withPostOptionPhase(async (scene: BattleScene) => { // Damage and status applied after dealer leaves (to make thematic sense) @@ -142,7 +145,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) - .withSceneMoneyRequirement(0, 3.5) + .withSceneMoneyRequirement(0, VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER) .withDialogue({ buttonLabel: `${namespace}.option.2.label`, buttonTooltip: `${namespace}.option.2.tooltip`, @@ -193,7 +196,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType); } - leaveEncounterWithoutBattle(scene); + leaveEncounterWithoutBattle(scene, true); }) .withPostOptionPhase(async (scene: BattleScene) => { // Status applied after dealer leaves (to make thematic sense) diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index b9f08b12ffd..bfccc46ee0f 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -1,22 +1,24 @@ import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; import { StatusEffect } from "#app/data/status-effect"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; -import { MoveRequirement } from "../mystery-encounter-requirements"; -import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; +import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; -import { PokemonMove } from "#app/field/pokemon"; +import { AiType, PokemonMove } from "#app/field/pokemon"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { BerryType } from "#enums/berry-type"; +import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; /** i18n namespace for the encounter */ const namespace = "mysteryEncounter:slumberingSnorlax"; @@ -38,7 +40,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = fileRoot: "pokemon", hasShadow: true, tint: 0.25, - scale: 1.5, + scale: 1.25, repeat: true, y: 5, }, @@ -58,10 +60,22 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = species: bossSpecies, isBoss: true, status: [StatusEffect.SLEEP, 5], // Extra turns on timer for Snorlax's start of fight moves - moveSet: [Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT] + moveSet: [Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT], + modifierConfigs: [ + { + modifier: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType, + stackCount: 2 + }, + { + modifier: generateModifierType(scene, modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType, + stackCount: 2 + }, + ], + mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), + aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep }; const config: EnemyPartyConfig = { - levelAdditiveMultiplier: 0.5, + levelAdditiveModifier: 0.5, pokemonConfigs: [pokemonConfig], }; encounter.enemyPartyConfigs = [config]; diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index bf976458fdd..10b0e5222b3 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -2,8 +2,8 @@ import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig import { randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MoneyRequirement, WaveModulusRequirement } from "../mystery-encounter-requirements"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MoneyRequirement, WaveModulusRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import Pokemon, { EnemyPokemon } from "#app/field/pokemon"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -20,12 +20,13 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounter:teleportingHijinks"; -const MONEY_COST_MULTIPLIER = 2.5; -const BIOME_CANDIDATES = [Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND]; +const MONEY_COST_MULTIPLIER = 1.75; +const BIOME_CANDIDATES = [Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND, Biome.WASTELAND, Biome.DOJO]; const MACHINE_INTERFACING_TYPES = [Type.ELECTRIC, Type.STEEL]; /** @@ -130,7 +131,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; // Init enemy - const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0); + const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true); const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); @@ -166,7 +167,7 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) { await showEncounterText(scene, `${namespace}.attacked`); // Init enemy - const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0); + const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true); const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); 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 new file mode 100644 index 00000000000..d4795c90453 --- /dev/null +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -0,0 +1,549 @@ +import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { trainerConfigs } from "#app/data/trainer-config"; +import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import BattleScene from "#app/battle-scene"; +import { randSeedShuffle } from "#app/utils"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { Biome } from "#enums/biome"; +import { TrainerType } from "#enums/trainer-type"; +import i18next from "i18next"; +import { Species } from "#enums/species"; +import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; +import { Nature } from "#enums/nature"; +import { Moves } from "#enums/moves"; +import { Type } from "#app/data/type"; +import { Stat } from "#enums/stat"; +import { PlayerPokemon } from "#app/field/pokemon"; +import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; +import { IEggOptions } from "#app/data/egg"; +import { EggSourceType } from "#enums/egg-source-types"; +import { EggTier } from "#enums/egg-type"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; +import { achvs } from "#app/system/achv"; + +/** the i18n namespace for the encounter */ +const namespace = "mysteryEncounter:expertPokemonBreeder"; + +const trainerNameKey = "trainerNames:expert_pokemon_breeder"; + +const FIRST_STAGE_EVOLUTION_WAVE = 30; +const SECOND_STAGE_EVOLUTION_WAVE = 45; +const FINAL_STAGE_EVOLUTION_WAVE = 60; + +const FRIENDSHIP_ADDED = 20; + +class BreederSpeciesEvolution { + species: Species; + evolution: number; + + constructor(species: Species, evolution: number) { + this.species = species; + this.evolution = evolution; + } +} + +const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [ + [Species.MUNCHLAX, new BreederSpeciesEvolution(Species.SNORLAX, SECOND_STAGE_EVOLUTION_WAVE)], + [Species.HAPPINY, new BreederSpeciesEvolution(Species.CHANSEY, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.BLISSEY, FINAL_STAGE_EVOLUTION_WAVE)], + [Species.MAGBY, new BreederSpeciesEvolution(Species.MAGMAR, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.MAGMORTAR, FINAL_STAGE_EVOLUTION_WAVE)], + [Species.ELEKID, new BreederSpeciesEvolution(Species.ELECTABUZZ, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ELECTIVIRE, FINAL_STAGE_EVOLUTION_WAVE)], + [Species.RIOLU, new BreederSpeciesEvolution(Species.LUCARIO, SECOND_STAGE_EVOLUTION_WAVE)], + [Species.BUDEW, new BreederSpeciesEvolution(Species.ROSELIA, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ROSERADE, FINAL_STAGE_EVOLUTION_WAVE)], + [Species.TOXEL, new BreederSpeciesEvolution(Species.TOXTRICITY, SECOND_STAGE_EVOLUTION_WAVE)], + [Species.MIME_JR, new BreederSpeciesEvolution(Species.GALAR_MR_MIME, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.MR_RIME, FINAL_STAGE_EVOLUTION_WAVE)] +]; + +const POOL_2_POKEMON: (Species | BreederSpeciesEvolution)[][] = [ + [Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.RAICHU, FINAL_STAGE_EVOLUTION_WAVE)], + [Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE)], + [Species.JYNX], + [Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE)], + [Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE)], + [Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE)], + [Species.IGGLYBUFF, new BreederSpeciesEvolution(Species.JIGGLYPUFF, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.WIGGLYTUFF, FINAL_STAGE_EVOLUTION_WAVE)], + [Species.AZURILL, new BreederSpeciesEvolution(Species.MARILL, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.AZUMARILL, FINAL_STAGE_EVOLUTION_WAVE)], + [Species.WYNAUT, new BreederSpeciesEvolution(Species.WOBBUFFET, SECOND_STAGE_EVOLUTION_WAVE)], + [Species.CHINGLING, new BreederSpeciesEvolution(Species.CHIMECHO, SECOND_STAGE_EVOLUTION_WAVE)], + [Species.BONSLY, new BreederSpeciesEvolution(Species.SUDOWOODO, SECOND_STAGE_EVOLUTION_WAVE)], + [Species.MANTYKE, new BreederSpeciesEvolution(Species.MANTINE, SECOND_STAGE_EVOLUTION_WAVE)] +]; + +/** + * The Expert Pokémon Breeder encounter. + * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3818 | GitHub Issue #3818} + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} + */ +export const TheExpertPokemonBreederEncounter: MysteryEncounter = + MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER) + .withEncounterTier(MysteryEncounterTier.ULTRA) + .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) + .withScenePartySizeRequirement(4, 6, true) // Must have at least 4 legal pokemon in party + .withIntroSpriteConfigs([]) // These are set in onInit() + .withIntroDialogue([ + { + text: `${namespace}.intro`, + }, + { + speaker: trainerNameKey, + text: `${namespace}.intro_dialogue`, + }, + ]) + .withOnInit((scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter!; + const waveIndex = scene.currentBattle.waveIndex; + // Calculates what trainers are available for battle in the encounter + + // If player is in space biome, uses special "Space" version of the trainer + encounter.enemyPartyConfigs = [ + getPartyConfig(scene) + ]; + + const cleffaSpecies = waveIndex < FIRST_STAGE_EVOLUTION_WAVE ? Species.CLEFFA : waveIndex < FINAL_STAGE_EVOLUTION_WAVE ? Species.CLEFAIRY : Species.CLEFABLE; + encounter.spriteConfigs = [ + { + spriteKey: cleffaSpecies.toString(), + fileRoot: "pokemon", + hasShadow: true, + repeat: true, + x: 14, + y: -2, + yShadow: -2 + }, + { + spriteKey: "expert_pokemon_breeder", + fileRoot: "trainer", + hasShadow: true, + x: -14, + y: 4, + yShadow: 2 + }, + ]; + + // Determine the 3 pokemon the player can battle with + let partyCopy = scene.getParty().slice(0); + partyCopy = partyCopy + .filter(p => p.isAllowedInBattle()) + .sort((a, b) => a.friendship - b.friendship); + + const pokemon1 = partyCopy[0]; + const pokemon2 = partyCopy[1]; + const pokemon3 = partyCopy[2]; + encounter.setDialogueToken("pokemon1Name", pokemon1.getNameToRender()); + encounter.setDialogueToken("pokemon2Name", pokemon2.getNameToRender()); + encounter.setDialogueToken("pokemon3Name", pokemon3.getNameToRender()); + + // Dialogue and egg calcs for Pokemon 1 + const [pokemon1CommonEggs, pokemon1RareEggs] = calculateEggRewardsForPokemon(pokemon1); + let pokemon1Tooltip = getEncounterText(scene, `${namespace}.option.1.tooltip_base`)!; + if (pokemon1RareEggs > 0) { + const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon1RareEggs, rarity: i18next.t("egg:greatTier") }); + pokemon1Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); + encounter.setDialogueToken("pokemon1RareEggs", eggsText); + } + if (pokemon1CommonEggs > 0) { + const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon1CommonEggs, rarity: i18next.t("egg:defaultTier") }); + pokemon1Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); + encounter.setDialogueToken("pokemon1CommonEggs", eggsText); + } + encounter.options[0].dialogue!.buttonTooltip = pokemon1Tooltip; + + // Dialogue and egg calcs for Pokemon 2 + const [pokemon2CommonEggs, pokemon2RareEggs] = calculateEggRewardsForPokemon(pokemon2); + let pokemon2Tooltip = getEncounterText(scene, `${namespace}.option.2.tooltip_base`)!; + if (pokemon2RareEggs > 0) { + const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon2RareEggs, rarity: i18next.t("egg:greatTier") }); + pokemon2Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); + encounter.setDialogueToken("pokemon2RareEggs", eggsText); + } + if (pokemon2CommonEggs > 0) { + const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") }); + pokemon2Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); + encounter.setDialogueToken("pokemon1CommonEggs", eggsText); + } + encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip; + + // Dialogue and egg calcs for Pokemon 3 + const [pokemon3CommonEggs, pokemon3RareEggs] = calculateEggRewardsForPokemon(pokemon3); + let pokemon3Tooltip = getEncounterText(scene, `${namespace}.option.3.tooltip_base`)!; + if (pokemon3RareEggs > 0) { + const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon3RareEggs, rarity: i18next.t("egg:greatTier") }); + pokemon3Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); + encounter.setDialogueToken("pokemon3RareEggs", eggsText); + } + if (pokemon3CommonEggs > 0) { + const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon3CommonEggs, rarity: i18next.t("egg:defaultTier") }); + pokemon3Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); + encounter.setDialogueToken("pokemon3CommonEggs", eggsText); + } + encounter.options[2].dialogue!.buttonTooltip = pokemon3Tooltip; + + encounter.misc = { + pokemon1, + pokemon1CommonEggs, + pokemon1RareEggs, + pokemon2, + pokemon2CommonEggs, + pokemon2RareEggs, + pokemon3, + pokemon3CommonEggs, + pokemon3RareEggs + }; + + return true; + }) + .withTitle(`${namespace}.title`) + .withDescription(`${namespace}.description`) + .withQuery(`${namespace}.query`) + .withOption( + MysteryEncounterOptionBuilder + .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) + .withDialogue({ + buttonLabel: `${namespace}.option.1.label`, + selected: [ + { + speaker: trainerNameKey, + text: `${namespace}.option.selected`, + }, + ], + }) + .withOptionPhase(async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter!; + // Spawn battle with first pokemon + const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; + + const { pokemon1, pokemon1CommonEggs, pokemon1RareEggs } = encounter.misc; + encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender()); + const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + + // Remove all Pokemon from the party except the chosen Pokemon + removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1); + + // Configure outro dialogue for egg rewards + encounter.dialogue.outro = [ + { + speaker: trainerNameKey, + text: `${namespace}.outro`, + }, + ]; + if (encounter.dialogueTokens.hasOwnProperty("pokemon1CommonEggs")) { + encounter.dialogue.outro.push({ + text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1CommonEggs"] }), + }); + } + if (encounter.dialogueTokens.hasOwnProperty("pokemon1RareEggs")) { + encounter.dialogue.outro.push({ + text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1RareEggs"] }), + }); + } + + initBattleWithEnemyConfig(scene, config); + }) + .withPostOptionPhase(async (scene: BattleScene) => { + // Give achievement if in Space biome + checkAchievement(scene); + // Give 20 friendship to the chosen pokemon + scene.currentBattle.mysteryEncounter!.misc.pokemon1.addFriendship(FRIENDSHIP_ADDED); + await restorePartyAndHeldItems(scene); + }) + .build() + ) + .withOption( + MysteryEncounterOptionBuilder + .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) + .withDialogue({ + buttonLabel: `${namespace}.option.2.label`, + selected: [ + { + speaker: trainerNameKey, + text: `${namespace}.option.selected`, + }, + ], + }) + .withOptionPhase(async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter!; + // Spawn battle with second pokemon + const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; + + const { pokemon2, pokemon2CommonEggs, pokemon2RareEggs } = encounter.misc; + encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender()); + const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + + // Remove all Pokemon from the party except the chosen Pokemon + removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2); + + // Configure outro dialogue for egg rewards + encounter.dialogue.outro = [ + { + speaker: trainerNameKey, + text: `${namespace}.outro`, + }, + ]; + if (encounter.dialogueTokens.hasOwnProperty("pokemon2CommonEggs")) { + encounter.dialogue.outro.push({ + text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2CommonEggs"] }), + }); + } + if (encounter.dialogueTokens.hasOwnProperty("pokemon2RareEggs")) { + encounter.dialogue.outro.push({ + text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2RareEggs"] }), + }); + } + + initBattleWithEnemyConfig(scene, config); + }) + .withPostOptionPhase(async (scene: BattleScene) => { + // Give achievement if in Space biome + checkAchievement(scene); + // Give 20 friendship to the chosen pokemon + scene.currentBattle.mysteryEncounter!.misc.pokemon2.addFriendship(FRIENDSHIP_ADDED); + await restorePartyAndHeldItems(scene); + }) + .build() + ) + .withOption( + MysteryEncounterOptionBuilder + .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) + .withDialogue({ + buttonLabel: `${namespace}.option.3.label`, + selected: [ + { + speaker: trainerNameKey, + text: `${namespace}.option.selected`, + }, + ], + }) + .withOptionPhase(async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter!; + // Spawn battle with third pokemon + const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; + + const { pokemon3, pokemon3CommonEggs, pokemon3RareEggs } = encounter.misc; + encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender()); + const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + + // Remove all Pokemon from the party except the chosen Pokemon + removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3); + + // Configure outro dialogue for egg rewards + encounter.dialogue.outro = [ + { + speaker: trainerNameKey, + text: `${namespace}.outro`, + }, + ]; + if (encounter.dialogueTokens.hasOwnProperty("pokemon3CommonEggs")) { + encounter.dialogue.outro.push({ + text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3CommonEggs"] }), + }); + } + if (encounter.dialogueTokens.hasOwnProperty("pokemon3RareEggs")) { + encounter.dialogue.outro.push({ + text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3RareEggs"] }), + }); + } + + initBattleWithEnemyConfig(scene, config); + }) + .withPostOptionPhase(async (scene: BattleScene) => { + // Give achievement if in Space biome + checkAchievement(scene); + // Give 20 friendship to the chosen pokemon + scene.currentBattle.mysteryEncounter!.misc.pokemon3.addFriendship(FRIENDSHIP_ADDED); + await restorePartyAndHeldItems(scene); + }) + .build() + ) + .withOutroDialogue([ + { + text: `${namespace}.outro`, + }, + ]) + .build(); + +function getPartyConfig(scene: BattleScene): EnemyPartyConfig { + // Bug type superfan trainer config + const waveIndex = scene.currentBattle.waveIndex; + const breederConfig = trainerConfigs[TrainerType.EXPERT_POKEMON_BREEDER].clone(); + breederConfig.name = i18next.t(trainerNameKey); + + // First mon is *always* this special cleffa + const cleffaSpecies = waveIndex < FIRST_STAGE_EVOLUTION_WAVE ? Species.CLEFFA : waveIndex < FINAL_STAGE_EVOLUTION_WAVE ? Species.CLEFAIRY : Species.CLEFABLE; + const baseConfig: EnemyPartyConfig = { + trainerType: TrainerType.EXPERT_POKEMON_BREEDER, + pokemonConfigs: [ + { + nickname: i18next.t(`${namespace}.cleffa_1_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }), + species: getPokemonSpecies(cleffaSpecies), + isBoss: false, + abilityIndex: 1, // Magic Guard + shiny: false, + nature: Nature.ADAMANT, + moveSet: [Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH], + ivs: [31, 31, 31, 31, 31, 31], + modifierConfigs: [ + { + modifier: generateModifierType(scene, modifierTypes.TERA_SHARD, [Type.STEEL]) as PokemonHeldItemModifierType, + }, + { + modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.ATK]) as PokemonHeldItemModifierType, + stackCount: 1 + Math.floor(waveIndex / 20), // +1 Protein every 20 waves + }, + { + modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType, + stackCount: 1 + Math.floor(waveIndex / 40), // +1 Carbos every 40 waves + }, + ] + } + ] + }; + + if (scene.arena.biomeType === Biome.SPACE) { + // All 3 members always Cleffa line, but different configs + baseConfig.pokemonConfigs!.push({ + nickname: i18next.t(`${namespace}.cleffa_2_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }), + species: getPokemonSpecies(cleffaSpecies), + isBoss: false, + abilityIndex: 1, // Magic Guard + shiny: true, + variant: 1, + nature: Nature.MODEST, + moveSet: [Moves.MOONBLAST, Moves.MYSTICAL_FIRE, Moves.ICE_BEAM, Moves.THUNDERBOLT], + ivs: [31, 31, 31, 31, 31, 31] + }, + { + nickname: i18next.t(`${namespace}.cleffa_3_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }), + species: getPokemonSpecies(cleffaSpecies), + isBoss: false, + abilityIndex: 2, // Friend Guard / Unaware + shiny: true, + variant: 2, + nature: Nature.BOLD, + moveSet: [Moves.TRI_ATTACK, Moves.STORED_POWER, Moves.TAKE_HEART, Moves.MOONLIGHT], + ivs: [31, 31, 31, 31, 31, 31] + }); + } else { + // Second member from pool 1 + const pool1Species = getSpeciesFromPool(POOL_1_POKEMON, waveIndex); + // Third member from pool 2 + const pool2Species = getSpeciesFromPool(POOL_2_POKEMON, waveIndex); + + baseConfig.pokemonConfigs!.push({ + species: getPokemonSpecies(pool1Species), + isBoss: false, + ivs: [31, 31, 31, 31, 31, 31] + }, + { + species: getPokemonSpecies(pool2Species), + isBoss: false, + ivs: [31, 31, 31, 31, 31, 31] + }); + } + + return baseConfig; +} + +function getSpeciesFromPool(speciesPool: (Species | BreederSpeciesEvolution)[][], waveIndex: number): Species { + const poolCopy = speciesPool.slice(0); + randSeedShuffle(poolCopy); + const speciesEvolutions = poolCopy.pop()!.slice(0); + let speciesObject = speciesEvolutions.pop()!; + while (speciesObject instanceof BreederSpeciesEvolution && speciesObject.evolution > waveIndex) { + speciesObject = speciesEvolutions.pop()!; + } + return speciesObject instanceof BreederSpeciesEvolution ? speciesObject.species : speciesObject; +} + +function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number] { + const bst = pokemon.calculateBaseStats().reduce((a, b) => a + b, 0); + // 1 point for every 20 points below 680 BST the pokemon is, (max 18, min 1) + const pointsFromBst = Math.min(Math.max(Math.floor((680 - bst) / 20), 1), 18); + + const rootSpecies = pokemon.species.getRootSpeciesId(true); + let pointsFromStarterTier = 0; + // 2 points for every 1 below 7 that the pokemon's starter tier is (max 12, min 0) + if (speciesStarters.hasOwnProperty(rootSpecies)) { + const starterTier = speciesStarters[rootSpecies]; + pointsFromStarterTier = Math.min(Math.max(Math.floor(7 - starterTier) * 2, 0), 12); + } + + // Maximum of 30 points + const totalPoints = Math.min(pointsFromStarterTier + pointsFromBst, 30); + + // 1 Rare egg for every 6 points + const numRares = Math.floor(totalPoints / 6); + // 1 Common egg for every point leftover + const numCommons = totalPoints % 6; + + return [numCommons, numRares]; +} + +function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number) { + const eggDescription = i18next.t(`${namespace}.title`) + ":\n" + i18next.t(trainerNameKey); + const eggOptions: IEggOptions[] = []; + + if (commonEggs > 0) { + for (let i = 0; i < commonEggs; i++) { + eggOptions.push({ + scene, + pulled: false, + sourceType: EggSourceType.EVENT, + eggDescriptor: eggDescription, + tier: EggTier.COMMON + }); + } + } + if (rareEggs > 0) { + for (let i = 0; i < rareEggs; i++) { + eggOptions.push({ + scene, + pulled: false, + sourceType: EggSourceType.EVENT, + eggDescriptor: eggDescription, + tier: EggTier.GREAT + }); + } + } + + return eggOptions; +} + +function removePokemonFromPartyAndStoreHeldItems(scene: BattleScene, encounter: MysteryEncounter, chosenPokemon: PlayerPokemon) { + const party = scene.getParty(); + const chosenIndex = party.indexOf(chosenPokemon); + party[chosenIndex] = party[0]; + party[0] = chosenPokemon; + encounter.misc.originalParty = scene.getParty().slice(1); + encounter.misc.originalPartyHeldItems = encounter.misc.originalParty + .map(p => p.getHeldItems()); + scene["party"] = [ + chosenPokemon + ]; +} + +function checkAchievement(scene: BattleScene) { + if (scene.arena.biomeType === Biome.SPACE) { + scene.validateAchv(achvs.BREEDERS_IN_SPACE); + } +} + +async function restorePartyAndHeldItems(scene: BattleScene) { + const encounter = scene.currentBattle.mysteryEncounter!; + // Restore original party + scene.getParty().push(...encounter.misc.originalParty); + + // Restore held items + const originalHeldItems = encounter.misc.originalPartyHeldItems; + originalHeldItems.forEach(pokemonHeldItemsList => { + pokemonHeldItemsList.forEach(heldItem => { + scene.addModifier(heldItem, true, false, false, true); + }); + }); + await scene.updateModifiers(true); +} diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index 16b0c421bd4..c26c6aa3b7f 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -2,8 +2,8 @@ import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, up import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MoneyRequirement } from "../mystery-encounter-requirements"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; import { Species } from "#enums/species"; @@ -15,11 +15,15 @@ import PokemonData from "#app/system/pokemon-data"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { Abilities } from "#enums/abilities"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounter:pokemonSalesman"; -const MAX_POKEMON_PRICE_MULTIPLIER = 6; +const MAX_POKEMON_PRICE_MULTIPLIER = 4; + +/** Odds of shiny magikarp will be 1/value */ +const SHINY_MAGIKARP_WEIGHT = 100; /** * Pokemon Salesman encounter. @@ -58,12 +62,12 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = const tries = 0; // Reroll any species that don't have HAs - while (isNullOrUndefined(species.abilityHidden) && tries < 5) { + while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) { species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5])); } let pokemon: PlayerPokemon; - if (isNullOrUndefined(species.abilityHidden) || randSeedInt(100) === 0) { + if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) { // If no HA mon found or you roll 1%, give shiny Magikarp species = getPokemonSpecies(Species.MAGIKARP); const hiddenIndex = species.ability2 ? 2 : 1; diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index 047aa0d83f6..55cb10644e8 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -2,7 +2,7 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounte import { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Species } from "#enums/species"; import { Nature } from "#app/data/nature"; @@ -36,6 +36,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party + .withMaxAllowedEncounters(1) .withHideWildIntroMessage(true) .withAutoHideIntroVisuals(false) .withIntroSpriteConfigs([ @@ -70,7 +71,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = // Calculate boss mon const config: EnemyPartyConfig = { - levelAdditiveMultiplier: 1, + levelAdditiveModifier: 1, disableSwitch: true, pokemonConfigs: [ { @@ -159,6 +160,11 @@ export const TheStrongStuffEncounter: MysteryEncounter = encounter.setDialogueToken("increaseValue", BST_INCREASE_VALUE.toString()); await showEncounterText(scene, `${namespace}.option.1.selected_2`, null, undefined, true); + encounter.dialogue.outro = [ + { + text: `${namespace}.outro`, + } + ]; setEncounterRewards(scene, { fillRemaining: true }); leaveEncounterWithoutBattle(scene, true); return true; @@ -192,6 +198,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = ignorePp: true }); + encounter.dialogue.outro = []; transitionMysteryEncounterIntroVisuals(scene, true, true, 500); await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); } diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 902aefcb490..60061efbc7a 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -2,7 +2,7 @@ import { EnemyPartyConfig, generateModifierType, generateModifierTypeOption, ini import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { TrainerType } from "#enums/trainer-type"; import { Species } from "#enums/species"; @@ -146,7 +146,7 @@ async function spawnNextTrainerOrEndEncounter(scene: BattleScene) { // Give 10x Voucher const newModifier = modifierTypes.VOUCHER_PREMIUM().newModifier(); - scene.addModifier(newModifier); + await scene.addModifier(newModifier); scene.playSound("item_fanfare"); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name })); diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 6c0f1706fa5..33d841b7f02 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -3,7 +3,7 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattl import { getNatureName, Nature } from "#app/data/nature"; import { speciesStarters } from "#app/data/pokemon-species"; import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; -import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { AbilityAttr } from "#app/system/game-data"; import PokemonData from "#app/system/pokemon-data"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; @@ -11,8 +11,8 @@ import { isNullOrUndefined, randSeedShuffle } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -34,6 +34,7 @@ export const TrainingSessionEncounter: MysteryEncounter = .withEncounterTier(MysteryEncounterTier.ULTRA) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party + .withFleeAllowed(false) .withHideWildIntroMessage(true) .withIntroSpriteConfigs([ { @@ -97,12 +98,7 @@ export const TrainingSessionEncounter: MysteryEncounter = 5 ); const modifiers = new ModifiersHolder(); - const config = getEnemyConfig( - scene, - playerPokemon, - segments, - modifiers - ); + const config = getEnemyConfig(scene, playerPokemon, segments, modifiers); scene.removePokemonFromPlayerParty(playerPokemon, false); const onBeforeRewardsPhase = () => { @@ -163,6 +159,7 @@ export const TrainingSessionEncounter: MysteryEncounter = // Add pokemon and mods back scene.getParty().push(playerPokemon); for (const mod of modifiers.value) { + mod.pokemonId = playerPokemon.id; scene.addModifier(mod, true, false, false, true); } scene.updateModifiers(true); @@ -230,17 +227,9 @@ export const TrainingSessionEncounter: MysteryEncounter = // Spawn medium training session with chosen pokemon // Every 40 waves, add +1 boss segment, capping at 6 - const segments = Math.min( - 2 + Math.floor(scene.currentBattle.waveIndex / 40), - 6 - ); + const segments = Math.min(2 + Math.floor(scene.currentBattle.waveIndex / 40), 6); const modifiers = new ModifiersHolder(); - const config = getEnemyConfig( - scene, - playerPokemon, - segments, - modifiers - ); + const config = getEnemyConfig(scene, playerPokemon, segments, modifiers); scene.removePokemonFromPlayerParty(playerPokemon, false); const onBeforeRewardsPhase = () => { @@ -377,6 +366,7 @@ export const TrainingSessionEncounter: MysteryEncounter = // Add pokemon and mods back scene.getParty().push(playerPokemon); for (const mod of modifiers.value) { + mod.pokemonId = playerPokemon.id; scene.addModifier(mod, true, false, false, true); } scene.updateModifiers(true); @@ -410,10 +400,12 @@ function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon, segmen playerPokemon.resetSummonData(); // Passes modifiers by reference - modifiers.value = playerPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); + modifiers.value = playerPokemon.getHeldItems(); const modifierConfigs = modifiers.value.map((mod) => { return { - modifier: mod + modifier: mod.clone(), + isTransferable: false, + stackCount: mod.stackCount }; }) as HeldModifierConfig[]; diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index ec6291f2a8c..d295c8ab548 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -2,8 +2,8 @@ import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleW import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { Species } from "#enums/species"; @@ -67,7 +67,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = moveSet: [Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH] }; const config: EnemyPartyConfig = { - levelAdditiveMultiplier: 1, + levelAdditiveModifier: 1, pokemonConfigs: [pokemonConfig], disableSwitch: true }; diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index f9148b87f9b..0816c9cd2a6 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -5,8 +5,8 @@ import Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { getPartyLuckValue } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MoveRequirement, PersistentModifierRequirement } from "../mystery-encounter-requirements"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MoveRequirement, PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { TrainerSlot } from "#app/data/trainer-config"; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 476cc98f503..ed8986d99bb 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -2,8 +2,8 @@ import { Type } from "#app/data/type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; +import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -14,7 +14,7 @@ import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, Pokemo import { achvs } from "#app/system/achv"; import { speciesEggMoves } from "#app/data/egg-moves"; import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; -import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; +import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; @@ -130,12 +130,7 @@ export const WeirdDreamEncounter: MysteryEncounter = return true; }) .withOnVisualsStart((scene: BattleScene) => { - // Change the bgm - scene.fadeOutBgm(3000, false); - scene.time.delayedCall(3000, () => { - scene.playBgm("mystery_encounter_weird_dream"); - }); - + scene.fadeAndSwitchBgm("mystery_encounter_weird_dream"); return true; }) .withOption( @@ -340,7 +335,7 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false); if (newStarterUnlocked) { atLeastOneNewStarter = true; - queueEncounterMessage(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() })); + await showEncounterText(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() })); } } diff --git a/src/data/mystery-encounters/mystery-encounter-option.ts b/src/data/mystery-encounters/mystery-encounter-option.ts index fb3daf53a8b..865877445c1 100644 --- a/src/data/mystery-encounters/mystery-encounter-option.ts +++ b/src/data/mystery-encounters/mystery-encounter-option.ts @@ -3,7 +3,7 @@ import { Moves } from "#app/enums/moves"; import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; import BattleScene from "#app/battle-scene"; import { Type } from "../type"; -import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "./mystery-encounter-requirements"; +import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index a204ea848da..2a5f6fda7e1 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -25,6 +25,9 @@ export interface EncounterStartOfBattleEffect { followUp?: boolean; } +const DEFAULT_MAX_ALLOWED_ENCOUNTERS = 2; +const DEFAULT_MAX_ALLOWED_ROGUE_ENCOUNTERS = 1; + /** * Used by {@linkcode MysteryEncounterBuilder} class to define required/optional properties on the {@linkcode MysteryEncounter} class when building. * @@ -42,6 +45,7 @@ export interface IMysteryEncounter { autoHideIntroVisuals: boolean; enterIntroVisualsFromRight: boolean; catchAllowed: boolean; + fleeAllowed: boolean; continuousEncounter: boolean; maxAllowedEncounters: number; hasBattleAnimationsWithoutTargets: boolean; @@ -110,6 +114,11 @@ export default class MysteryEncounter implements IMysteryEncounter { * Default false */ catchAllowed: boolean; + /** + * If true, allows fleeing from a wild encounter (trainer battle MEs auto-disable fleeing) + * Default true + */ + fleeAllowed: boolean; /** * If true, encounter will continuously run through multiple battles/puzzles/etc. instead of going to next wave * MUST EVENTUALLY BE DISABLED TO CONTINUE TO NEXT WAVE @@ -246,8 +255,8 @@ export default class MysteryEncounter implements IMysteryEncounter { this.encounterTier = this.encounterTier ?? MysteryEncounterTier.COMMON; this.dialogue = this.dialogue ?? {}; this.spriteConfigs = this.spriteConfigs ? [...this.spriteConfigs] : []; - // Default max is 1 for ROGUE encounters, 3 for others - this.maxAllowedEncounters = this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE ? 1 : 3; + // Default max is 1 for ROGUE encounters, 2 for others + this.maxAllowedEncounters = this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE ? DEFAULT_MAX_ALLOWED_ROGUE_ENCOUNTERS : DEFAULT_MAX_ALLOWED_ENCOUNTERS; this.encounterMode = MysteryEncounterMode.DEFAULT; this.requirements = this.requirements ? this.requirements : []; this.hideBattleIntroMessage = this.hideBattleIntroMessage ?? false; @@ -520,6 +529,7 @@ export class MysteryEncounterBuilder implements Partial { enterIntroVisualsFromRight: boolean = false; continuousEncounter: boolean = false; catchAllowed: boolean = false; + fleeAllowed: boolean = true; lockEncounterRewardTiers: boolean = false; startOfBattleEffectsComplete: boolean = false; hasBattleAnimationsWithoutTargets: boolean = false; @@ -580,8 +590,8 @@ export class MysteryEncounterBuilder implements Partial { * There should be at least 2 options defined and no more than 4. * If complex use {@linkcode MysteryEncounterBuilder.withOption} * - * @param dialogue - {@linkcode OptionTextDisplay} - * @param callback - {@linkcode OptionPhaseCallback} + * @param dialogue {@linkcode OptionTextDisplay} + * @param callback {@linkcode OptionPhaseCallback} * @returns */ withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick { @@ -732,7 +742,7 @@ export class MysteryEncounterBuilder implements Partial { * * @param min min wave (or exact size if only min is given) * @param max optional max size. If not given, defaults to min => exact wave - * @param excludeFainted - if true, only counts unfainted mons + * @param excludeFainted if true, only counts unfainted mons * @returns */ withScenePartySizeRequirement(min: number, max?: number, excludeFainted: boolean = false): this & Required> { @@ -798,7 +808,7 @@ export class MysteryEncounterBuilder implements Partial { * NOTE: If rewards are dependent on options selected, runtime data, etc., * It may be better to programmatically set doEncounterRewards elsewhere. * There is a helper function in mystery-encounter utils, setEncounterRewards(), which can be called programmatically to set rewards - * @param doEncounterRewards - synchronous callback function to perform during rewards phase of the encounter + * @param doEncounterRewards Synchronous callback function to perform during rewards phase of the encounter * @returns */ withRewards(doEncounterRewards: (scene: BattleScene) => boolean): this & Required> { @@ -812,7 +822,7 @@ export class MysteryEncounterBuilder implements Partial { * NOTE: If rewards are dependent on options selected, runtime data, etc., * It may be better to programmatically set doEncounterExp elsewhere. * There is a helper function in mystery-encounter utils, setEncounterExp(), which can be called programmatically to set rewards - * @param doEncounterExp - synchronous callback function to perform during rewards phase of the encounter + * @param doEncounterExp Synchronous callback function to perform during rewards phase of the encounter * @returns */ withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required> { @@ -823,7 +833,7 @@ export class MysteryEncounterBuilder implements Partial { * Can be used to perform init logic before intro visuals are shown and before the MysteryEncounterPhase begins * Useful for performing things like procedural generation of intro sprites, etc. * - * @param onInit - synchronous callback function to perform as soon as the encounter is selected for the next phase + * @param onInit Synchronous callback function to perform as soon as the encounter is selected for the next phase * @returns */ withOnInit(onInit: (scene: BattleScene) => boolean): this & Required> { @@ -833,7 +843,7 @@ export class MysteryEncounterBuilder implements Partial { /** * Can be used to perform some extra logic (usually animations) when the enemy field is finished sliding in * - * @param onVisualsStart - synchronous callback function to perform as soon as the enemy field finishes sliding in + * @param onVisualsStart Synchronous callback function to perform as soon as the enemy field finishes sliding in * @returns */ withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required> { @@ -843,7 +853,7 @@ export class MysteryEncounterBuilder implements Partial { /** * Can set whether catching is allowed or not on the encounter * This flag can also be programmatically set inside option event functions or elsewhere - * @param catchAllowed - if true, allows enemy pokemon to be caught during the encounter + * @param catchAllowed If `true`, allows enemy pokemon to be caught during the encounter * @returns */ withCatchAllowed(catchAllowed: boolean): this & Required> { @@ -851,7 +861,16 @@ export class MysteryEncounterBuilder implements Partial { } /** - * @param hideBattleIntroMessage - if true, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter + * Can set whether fleeing is allowed or not on the encounter + * @param fleeAllowed If `false`, prevents fleeing from a wild battle (trainer battle MEs already have flee disabled) + * @returns + */ + withFleeAllowed(fleeAllowed: boolean): this & Required> { + return Object.assign(this, { fleeAllowed }); + } + + /** + * @param hideBattleIntroMessage If `true`, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter * @returns */ withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required> { @@ -859,7 +878,7 @@ export class MysteryEncounterBuilder implements Partial { } /** - * @param autoHideIntroVisuals - if false, will not hide the intro visuals that are displayed at the beginning of encounter + * @param autoHideIntroVisuals If `false`, will not hide the intro visuals that are displayed at the beginning of encounter * @returns */ withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required> { @@ -867,7 +886,7 @@ export class MysteryEncounterBuilder implements Partial { } /** - * @param enterIntroVisualsFromRight - If true, will slide in intro visuals from the right side of the screen. If false, slides in from left, as normal + * @param enterIntroVisualsFromRight If `true`, will slide in intro visuals from the right side of the screen. If false, slides in from left, as normal * Default false * @returns */ @@ -878,7 +897,7 @@ export class MysteryEncounterBuilder implements Partial { /** * Add a title for the encounter * - * @param title - title of the encounter + * @param title Title of the encounter * @returns */ withTitle(title: string): this { @@ -898,7 +917,7 @@ export class MysteryEncounterBuilder implements Partial { /** * Add a description of the encounter * - * @param description - description of the encounter + * @param description Description of the encounter * @returns */ withDescription(description: string): this { @@ -918,7 +937,7 @@ export class MysteryEncounterBuilder implements Partial { /** * Add a query for the encounter * - * @param query - query to use for the encounter + * @param query Query to use for the encounter * @returns */ withQuery(query: string): this { @@ -938,7 +957,7 @@ export class MysteryEncounterBuilder implements Partial { /** * Add outro dialogue/s for the encounter * - * @param dialogue - outro dialogue/s + * @param dialogue Outro dialogue(s) * @returns */ withOutroDialogue(dialogue: MysteryEncounterDialogue["outro"] = []): this { diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index d235ff86861..cc2eaf234c4 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -31,6 +31,7 @@ import { BugTypeSuperfanEncounter } from "#app/data/mystery-encounters/encounter import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fun-and-games-encounter"; import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter"; import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter"; +import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter"; /** * Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT @@ -184,7 +185,8 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [ MysteryEncounterType.SHADY_VITAMIN_DEALER, MysteryEncounterType.THE_POKEMON_SALESMAN, MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, - MysteryEncounterType.THE_WINSTRATE_CHALLENGE + MysteryEncounterType.THE_WINSTRATE_CHALLENGE, + MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER ]; const civilizationBiomeEncounters: MysteryEncounterType[] = [ @@ -238,7 +240,6 @@ export const mysteryEncountersByBiome = new Map([ MysteryEncounterType.SAFARI_ZONE, MysteryEncounterType.ABSOLUTE_AVARICE ]], - [Biome.SEA, [ MysteryEncounterType.LOST_AT_SEA ]], @@ -275,7 +276,9 @@ export const mysteryEncountersByBiome = new Map([ [Biome.ABYSS, [ MysteryEncounterType.DANCING_LESSONS ]], - [Biome.SPACE, []], + [Biome.SPACE, [ + MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER + ]], [Biome.CONSTRUCTION_SITE, []], [Biome.JUNGLE, [ MysteryEncounterType.SAFARI_ZONE @@ -319,6 +322,7 @@ export function initMysteryEncounters() { allMysteryEncounters[MysteryEncounterType.FUN_AND_GAMES] = FunAndGamesEncounter; allMysteryEncounters[MysteryEncounterType.UNCOMMON_BREED] = UncommonBreedEncounter; allMysteryEncounters[MysteryEncounterType.GLOBAL_TRADE_SYSTEM] = GlobalTradeSystemEncounter; + allMysteryEncounters[MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER] = TheExpertPokemonBreederEncounter; // Add extreme encounters to biome map extremeBiomeEncounters.forEach(encounter => { diff --git a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts index bbdf2ee5a4d..a0b4edd4a36 100644 --- a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts +++ b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts @@ -2,7 +2,7 @@ import BattleScene from "#app/battle-scene"; import { Moves } from "#app/enums/moves"; import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { isNullOrUndefined } from "#app/utils"; -import { EncounterPokemonRequirement } from "../mystery-encounter-requirements"; +import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; /** * {@linkcode CanLearnMoveRequirement} options diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 2cd369fbaad..187de3c93c4 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -3,7 +3,7 @@ import { biomeLinks, BiomePoolTier } from "#app/data/biomes"; import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import Pokemon, { FieldPosition, PlayerPokemon, PokemonMove, PokemonSummonData } from "#app/field/pokemon"; +import Pokemon, { AiType, FieldPosition, PlayerPokemon, PokemonMove, PokemonSummonData } from "#app/field/pokemon"; import { CustomModifierSettings, ModifierPoolType, ModifierType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; import PokemonData from "#app/system/pokemon-data"; @@ -36,6 +36,7 @@ import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { GameOverPhase } from "#app/phases/game-over-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; +import { Variant } from "#app/data/variant"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -67,6 +68,7 @@ export function doTrainerExclamation(scene: BattleScene) { export interface EnemyPokemonConfig { species: PokemonSpecies; isBoss: boolean; + nickname?: string; bossSegments?: number; bossSegmentModifier?: number; // Additive to the determined segment number mysteryEncounterPokemonData?: MysteryEncounterPokemonData; @@ -79,27 +81,32 @@ export interface EnemyPokemonConfig { nature?: Nature; ivs?: [number, number, number, number, number, number]; shiny?: boolean; + /** Is only checked if Pokemon is shiny */ + variant?: Variant; /** Can set just the status, or pass a timer on the status turns */ status?: StatusEffect | [StatusEffect, number]; mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; modifierConfigs?: HeldModifierConfig[]; tags?: BattlerTagType[]; dataSource?: PokemonData; + aiType?: AiType; } export interface EnemyPartyConfig { - /** Formula for enemy: level += waveIndex / 10 * levelAdditive */ - levelAdditiveMultiplier?: number; + /** Formula for enemy level: level += waveIndex / 10 * levelAdditiveModifier */ + levelAdditiveModifier?: number; doubleBattle?: boolean; /** Generates trainer battle solely off trainer type */ trainerType?: TrainerType; /** More customizable option for configuring trainer battle */ trainerConfig?: TrainerConfig; pokemonConfigs?: EnemyPokemonConfig[]; - /** True for female trainer, false for male */ + /** `true` for female trainer, false for male */ female?: boolean; - /** True will prevent player from switching */ + /** `true` will prevent player from switching */ disableSwitch?: boolean; + /** `true` or leaving undefined will increment dex seen count for the encounter battle, `false` will not */ + countAsSeen?: boolean; } /** @@ -156,10 +163,10 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: // ME levels are modified by an additive value that scales with wave index // Base scaling: Every 10 waves, modifier gets +1 level - // This can be amplified or counteracted by setting levelAdditiveMultiplier in config - // levelAdditiveMultiplier value of 0.5 will halve the modifier scaling, 2 will double it, etc. + // This can be amplified or counteracted by setting levelAdditiveModifier in config + // levelAdditiveModifier value of 0.5 will halve the modifier scaling, 2 will double it, etc. // Leaving null/undefined will disable level scaling - const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveMultiplier) ? partyConfig.levelAdditiveMultiplier! : 0; + const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveModifier) ? partyConfig.levelAdditiveModifier! : 0; const additive = Math.max(Math.round((scene.currentBattle.waveIndex / 10) * mult), 0); battle.enemyLevels = battle.enemyLevels.map(level => level + additive); @@ -210,13 +217,18 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: enemyPokemon.resetSummonData(); } - if (!loaded) { + if (!loaded && isNullOrUndefined(partyConfig.countAsSeen) || partyConfig.countAsSeen) { scene.gameData.setPokemonSeen(enemyPokemon, true, !!(trainerType || trainerConfig)); } if (partyConfig?.pokemonConfigs && e < partyConfig.pokemonConfigs.length) { const config = partyConfig.pokemonConfigs[e]; + // Set form + if (!isNullOrUndefined(config.nickname)) { + enemyPokemon.nickname = btoa(unescape(encodeURIComponent(config.nickname!))); + } + // Generate new id, reset status and HP in case using data source if (config.dataSource) { enemyPokemon.id = Utils.randSeedInt(4294967296); @@ -232,6 +244,11 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: enemyPokemon.shiny = config.shiny!; } + // Set Variant + if (enemyPokemon.shiny && !isNullOrUndefined(config.variant)) { + enemyPokemon.variant = config.variant!; + } + // Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.) if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) { enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData!; @@ -286,6 +303,11 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: enemyPokemon.summonData.gender = config.gender!; } + // Set AI type + if (!isNullOrUndefined(config.aiType)) { + enemyPokemon.aiType = config.aiType!; + } + // Set moves if (config?.moveSet && config.moveSet.length > 0) { const moves = config.moveSet.map(m => new PokemonMove(m)); @@ -307,6 +329,9 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: // Requires re-priming summon data to update everything properly enemyPokemon.primeSummonData(enemyPokemon.summonData); + if (enemyPokemon.isShiny() && !enemyPokemon["shinySparkle"]) { + enemyPokemon.initShinySparkle(); + } enemyPokemon.initBattleInfo(); enemyPokemon.getBattleInfo().initInfo(enemyPokemon); enemyPokemon.generateName(); @@ -702,19 +727,19 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase: if (encounter.continuousEncounter || doNotContinue) { return; } else if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) { - scene.pushPhase(new EggLapsePhase(scene)); scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); + scene.pushPhase(new EggLapsePhase(scene)); } else if (!scene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) { scene.pushPhase(new BattleEndPhase(scene)); if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { scene.pushPhase(new TrainerVictoryPhase(scene)); } if (scene.gameMode.isEndless || !scene.gameMode.isWaveFinal(scene.currentBattle.waveIndex)) { + scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); if (!encounter.doContinueEncounter) { // Only lapse eggs once for multi-battle encounters scene.pushPhase(new EggLapsePhase(scene)); } - scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); } } } diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index bac8bded9ba..86c86010c29 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -19,6 +19,10 @@ import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifi import { Gender } from "#app/data/gender"; import { PermanentStat } from "#enums/stat"; import { VictoryPhase } from "#app/phases/victory-phase"; +import { SummaryUiMode } from "#app/ui/summary-ui-handler"; + +/** Will give +1 level every 10 waves */ +export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1; /** * Gets the sprite key and file root for a given PokemonSpecies (accounts for gender, shiny, variants, forms, and experimental) @@ -289,10 +293,12 @@ export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: numb */ export async function applyModifierTypeToPlayerPokemon(scene: BattleScene, pokemon: PlayerPokemon, modType: PokemonHeldItemModifierType, fallbackModifierType?: PokemonHeldItemModifierType) { // Check if the Pokemon has max stacks of that item already + const modifier = modType.newModifier(pokemon); const existing = scene.findModifier(m => ( m instanceof PokemonHeldItemModifier && m.type.id === modType.id && - m.pokemonId === pokemon.id + m.pokemonId === pokemon.id && + m.matchType(modifier) )) as PokemonHeldItemModifier; // At max stacks @@ -305,7 +311,6 @@ export async function applyModifierTypeToPlayerPokemon(scene: BattleScene, pokem return applyModifierTypeToPlayerPokemon(scene, pokemon, fallbackModifierType); } - const modifier = modType.newModifier(pokemon); await scene.addModifier(modifier, false, false, false, true); } @@ -327,7 +332,7 @@ export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon, const _3m = 3 * pokemon.getMaxHp(); const _2h = 2 * pokemon.hp; const catchRate = pokemon.species.catchRate; - const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType); + const pokeballMultiplier = getPokeballCatchMultiplier(pokeballType); const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1; const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier); ballTwitchRate = Math.round(65536 / Math.sqrt(Math.sqrt(255 / x))); @@ -501,8 +506,6 @@ function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number, * @param isObtain */ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite | null, pokeballType: PokeballType, showCatchObtainMessage: boolean = true, isObtain: boolean = false): Promise { - scene.unshiftPhase(new VictoryPhase(scene, pokemon.id, true)); - const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) { @@ -528,6 +531,11 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po return new Promise(resolve => { const doPokemonCatchMenu = () => { const end = () => { + // Ensure the pokemon is in the enemy party in all situations + if (!scene.getEnemyParty().some(p => p.id === pokemon.id)) { + scene.getEnemyParty().push(pokemon); + } + scene.unshiftPhase(new VictoryPhase(scene, pokemon.id, true)); scene.pokemonInfoContainer.hide(); if (pokeball) { removePb(scene, pokeball); @@ -539,8 +547,8 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po scene.field.remove(pokemon, true); } }; - const addToParty = () => { - const newPokemon = pokemon.addToParty(pokeballType); + const addToParty = (slotIndex?: number) => { + const newPokemon = pokemon.addToParty(pokeballType, slotIndex); const modifiers = scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); if (scene.getParty().filter(p => p.isShiny()).length === 6) { scene.validateAchv(achvs.SHINY_PARTY); @@ -559,12 +567,19 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po if (scene.getParty().length === 6) { const promptRelease = () => { scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => { - scene.pokemonInfoContainer.makeRoomForConfirmUi(); + scene.pokemonInfoContainer.makeRoomForConfirmUi(1, true); scene.ui.setMode(Mode.CONFIRM, () => { - scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: number, _option: PartyOption) => { + const newPokemon = scene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon); + scene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => { + scene.ui.setMode(Mode.MESSAGE).then(() => { + promptRelease(); + }); + }, false); + }, () => { + scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => { scene.ui.setMode(Mode.MESSAGE).then(() => { if (slotIndex < 6) { - addToParty(); + addToParty(slotIndex); } else { promptRelease(); } @@ -575,7 +590,7 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po removePokemon(); end(); }); - }); + }, "fullParty"); }); }; promptRelease(); @@ -711,13 +726,50 @@ export function getGoldenBugNetSpecies(): PokemonSpecies { const roll = randSeedInt(totalWeight); let w = 0; - for (const species of GOLDEN_BUG_NET_SPECIES_POOL) { - w += species[1]; + for (const speciesWeightPair of GOLDEN_BUG_NET_SPECIES_POOL) { + w += speciesWeightPair[1]; if (roll < w) { - return getPokemonSpecies(species); + return getPokemonSpecies(speciesWeightPair[0]); } } // Defaults to Scyther return getPokemonSpecies(Species.SCYTHER); } + +/** + * Generates a Pokemon level for a given wave, with an option to increase/decrease by a scaling modifier + * @param scene + * @param levelAdditiveModifier Default 0. will add +(1 level / 10 waves * levelAdditiveModifier) to the level calculation + */ +export function getEncounterPokemonLevelForWave(scene: BattleScene, levelAdditiveModifier: number = 0) { + const currentBattle = scene.currentBattle; + // Default to use the first generated level from enemyLevels, or generate a new one if it DNE + const baseLevel = currentBattle.enemyLevels && currentBattle.enemyLevels?.length > 0 ? currentBattle.enemyLevels[0] : currentBattle.getLevelForWave(); + + // Add a level scaling modifier that is (+1 level per 10 waves) * levelAdditiveModifier + return baseLevel + Math.max(Math.round((currentBattle.waveIndex / 10) * levelAdditiveModifier), 0); +} + +export async function addPokemonDataToDexAndValidateAchievements(scene: BattleScene, pokemon: PlayerPokemon) { + const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); + + if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) { + scene.validateAchv(achvs.HIDDEN_ABILITY); + } + + if (pokemon.species.subLegendary) { + scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); + } + + if (pokemon.species.legendary) { + scene.validateAchv(achvs.CATCH_LEGENDARY); + } + + if (pokemon.species.mythical) { + scene.validateAchv(achvs.CATCH_MYTHICAL); + } + + scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); + return scene.gameData.setPokemonCaught(pokemon, true, false, false); +} diff --git a/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts b/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts index fd9d43829e5..fcadb101817 100644 --- a/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts +++ b/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts @@ -103,7 +103,7 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke scene.time.delayedCall(1000, () => { pokemonEvoTintSprite.setScale(0.25); pokemonEvoTintSprite.setVisible(true); - doCycle(scene, 2, 6, pokemonTintSprite, pokemonEvoTintSprite).then(() => { + doCycle(scene, 1.5, 6, pokemonTintSprite, pokemonEvoTintSprite).then(() => { pokemonEvoSprite.setVisible(true); doCircleInward(scene, transformationBaseBg, transformationContainer, xOffset, yOffset); @@ -115,7 +115,7 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke delay: 150, easing: "Sine.easeIn", onComplete: () => { - scene.time.delayedCall(2500, () => { + scene.time.delayedCall(3000, () => { resolve(); scene.tweens.add({ targets: pokemonEvoSprite, diff --git a/src/data/pokemon-level-moves.ts b/src/data/pokemon-level-moves.ts index b56bab724be..b5608093df2 100644 --- a/src/data/pokemon-level-moves.ts +++ b/src/data/pokemon-level-moves.ts @@ -19498,6 +19498,108 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [ 51, Moves.BELCH ], ], }, + [Species.REVAVROOM]: { + 1: [ + [ EVOLVE_MOVE, Moves.WICKED_TORQUE ], + [ EVOLVE_MOVE, Moves.SHIFT_GEAR ], + [ 1, Moves.LICK ], + [ 1, Moves.POISON_GAS ], + [ 1, Moves.MAGNET_RISE ], + [ 4, Moves.SMOG ], + [ 7, Moves.TAUNT ], + [ 10, Moves.ASSURANCE ], + [ 13, Moves.SLUDGE ], + [ 17, Moves.GYRO_BALL ], + [ 21, Moves.HEADBUTT ], + [ 25, Moves.SCREECH ], + [ 28, Moves.IRON_HEAD ], + [ 32, Moves.SWAGGER ], + [ 36, Moves.POISON_JAB ], + [ 46, Moves.UPROAR ], + [ 52, Moves.SPIN_OUT ], + [ 58, Moves.GUNK_SHOT ], + ], + 2: [ + [ EVOLVE_MOVE, Moves.BLAZING_TORQUE ], + [ EVOLVE_MOVE, Moves.SHIFT_GEAR ], + [ 1, Moves.LICK ], + [ 1, Moves.POISON_GAS ], + [ 1, Moves.MAGNET_RISE ], + [ 4, Moves.SMOG ], + [ 7, Moves.TAUNT ], + [ 10, Moves.ASSURANCE ], + [ 13, Moves.SLUDGE ], + [ 17, Moves.GYRO_BALL ], + [ 21, Moves.HEADBUTT ], + [ 25, Moves.SCREECH ], + [ 28, Moves.IRON_HEAD ], + [ 32, Moves.SWAGGER ], + [ 36, Moves.POISON_JAB ], + [ 46, Moves.UPROAR ], + [ 52, Moves.SPIN_OUT ], + [ 58, Moves.GUNK_SHOT ], + ], + 3: [ + [ EVOLVE_MOVE, Moves.NOXIOUS_TORQUE ], + [ EVOLVE_MOVE, Moves.SHIFT_GEAR ], + [ 1, Moves.LICK ], + [ 1, Moves.POISON_GAS ], + [ 1, Moves.MAGNET_RISE ], + [ 4, Moves.SMOG ], + [ 7, Moves.TAUNT ], + [ 10, Moves.ASSURANCE ], + [ 13, Moves.SLUDGE ], + [ 17, Moves.GYRO_BALL ], + [ 21, Moves.HEADBUTT ], + [ 25, Moves.SCREECH ], + [ 28, Moves.IRON_HEAD ], + [ 32, Moves.SWAGGER ], + [ 36, Moves.POISON_JAB ], + [ 46, Moves.UPROAR ], + [ 52, Moves.SPIN_OUT ], + [ 58, Moves.GUNK_SHOT ], + ], + 4: [ + [ EVOLVE_MOVE, Moves.MAGICAL_TORQUE ], + [ EVOLVE_MOVE, Moves.SHIFT_GEAR ], + [ 1, Moves.LICK ], + [ 1, Moves.POISON_GAS ], + [ 1, Moves.MAGNET_RISE ], + [ 4, Moves.SMOG ], + [ 7, Moves.TAUNT ], + [ 10, Moves.ASSURANCE ], + [ 13, Moves.SLUDGE ], + [ 17, Moves.GYRO_BALL ], + [ 21, Moves.HEADBUTT ], + [ 25, Moves.SCREECH ], + [ 28, Moves.IRON_HEAD ], + [ 32, Moves.SWAGGER ], + [ 36, Moves.POISON_JAB ], + [ 46, Moves.UPROAR ], + [ 52, Moves.SPIN_OUT ], + [ 58, Moves.GUNK_SHOT ], + ], + 5: [ + [ EVOLVE_MOVE, Moves.COMBAT_TORQUE ], + [ EVOLVE_MOVE, Moves.SHIFT_GEAR ], + [ 1, Moves.LICK ], + [ 1, Moves.POISON_GAS ], + [ 1, Moves.MAGNET_RISE ], + [ 4, Moves.SMOG ], + [ 7, Moves.TAUNT ], + [ 10, Moves.ASSURANCE ], + [ 13, Moves.SLUDGE ], + [ 17, Moves.GYRO_BALL ], + [ 21, Moves.HEADBUTT ], + [ 25, Moves.SCREECH ], + [ 28, Moves.IRON_HEAD ], + [ 32, Moves.SWAGGER ], + [ 36, Moves.POISON_JAB ], + [ 46, Moves.UPROAR ], + [ 52, Moves.SPIN_OUT ], + [ 58, Moves.GUNK_SHOT ], + ], + }, [Species.PALDEA_TAUROS]: { 1: [ [ 1, Moves.TACKLE ], diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 4eb526eeb2b..b8ddd826035 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -2545,7 +2545,14 @@ export function initSpecies() { new PokemonForm("Hero Form", "hero", Type.WATER, null, 1.8, 97.4, Abilities.ZERO_TO_HERO, Abilities.NONE, Abilities.ZERO_TO_HERO, 650, 100, 160, 97, 106, 87, 100, 45, 50, 160), ), new PokemonSpecies(Species.VAROOM, 9, false, false, false, "Single-Cyl Pokémon", Type.STEEL, Type.POISON, 1, 35, Abilities.OVERCOAT, Abilities.NONE, Abilities.SLOW_START, 300, 45, 70, 63, 30, 45, 47, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(Species.REVAVROOM, 9, false, false, false, "Multi-Cyl Pokémon", Type.STEEL, Type.POISON, 1.8, 120, Abilities.OVERCOAT, Abilities.NONE, Abilities.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(Species.REVAVROOM, 9, false, false, false, "Multi-Cyl Pokémon", Type.STEEL, Type.POISON, 1.8, 120, Abilities.OVERCOAT, Abilities.NONE, Abilities.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Normal", "", Type.STEEL, Type.POISON, 1.8, 120, Abilities.OVERCOAT, Abilities.NONE, Abilities.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, false, null, true), + new PokemonForm("Segin Starmobile", "segin-starmobile", Type.STEEL, Type.DARK, 1.8, 240, Abilities.INTIMIDATE, Abilities.NONE, Abilities.INTIMIDATE, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), + new PokemonForm("Schedar Starmobile", "schedar-starmobile", Type.STEEL, Type.FIRE, 1.8, 240, Abilities.SPEED_BOOST, Abilities.NONE, Abilities.SPEED_BOOST, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), + new PokemonForm("Navi Starmobile", "navi-starmobile", Type.STEEL, Type.POISON, 1.8, 240, Abilities.TOXIC_DEBRIS, Abilities.NONE, Abilities.TOXIC_DEBRIS, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), + new PokemonForm("Ruchbah Starmobile", "ruchbah-starmobile", Type.STEEL, Type.FAIRY, 1.8, 240, Abilities.MISTY_SURGE, Abilities.NONE, Abilities.MISTY_SURGE, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), + new PokemonForm("Caph Starmobile", "caph-starmobile", Type.STEEL, Type.FIGHTING, 1.8, 240, Abilities.STAMINA, Abilities.NONE, Abilities.STAMINA, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), + ), new PokemonSpecies(Species.CYCLIZAR, 9, false, false, false, "Mount Pokémon", Type.DRAGON, Type.NORMAL, 1.6, 63, Abilities.SHED_SKIN, Abilities.NONE, Abilities.REGENERATOR, 501, 70, 95, 65, 85, 65, 121, 190, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.ORTHWORM, 9, false, false, false, "Earthworm Pokémon", Type.STEEL, null, 2.5, 310, Abilities.EARTH_EATER, Abilities.NONE, Abilities.SAND_VEIL, 480, 70, 85, 145, 60, 55, 65, 25, 50, 240, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.GLIMMET, 9, false, false, false, "Ore Pokémon", Type.ROCK, Type.POISON, 0.7, 8, Abilities.TOXIC_DEBRIS, Abilities.NONE, Abilities.CORROSION, 350, 48, 35, 42, 105, 60, 60, 70, 50, 70, GrowthRate.MEDIUM_SLOW, 50, false), @@ -3396,7 +3403,7 @@ export const starterPassiveAbilities = { [Species.POLIWAG]: Abilities.NO_GUARD, [Species.ABRA]: Abilities.PSYCHIC_SURGE, [Species.MACHOP]: Abilities.QUICK_FEET, - [Species.BELLSPROUT]: Abilities.PROTOSYNTHESIS, + [Species.BELLSPROUT]: Abilities.FLOWER_GIFT, [Species.TENTACOOL]: Abilities.TOXIC_CHAIN, [Species.GEODUDE]: Abilities.DRY_SKIN, [Species.PONYTA]: Abilities.MAGIC_GUARD, @@ -3424,7 +3431,7 @@ export const starterPassiveAbilities = { [Species.STARYU]: Abilities.REGENERATOR, [Species.SCYTHER]: Abilities.TINTED_LENS, [Species.PINSIR]: Abilities.TINTED_LENS, - [Species.TAUROS]: Abilities.SCRAPPY, + [Species.TAUROS]: Abilities.STAMINA, [Species.MAGIKARP]: Abilities.MULTISCALE, [Species.LAPRAS]: Abilities.LIGHTNING_ROD, [Species.DITTO]: Abilities.ADAPTABILITY, @@ -3492,7 +3499,7 @@ export const starterPassiveAbilities = { [Species.LARVITAR]: Abilities.SAND_RUSH, [Species.LUGIA]: Abilities.DELTA_STREAM, [Species.HO_OH]: Abilities.MAGIC_GUARD, - [Species.CELEBI]: Abilities.GRASSY_SURGE, + [Species.CELEBI]: Abilities.PSYCHIC_SURGE, [Species.TREECKO]: Abilities.TINTED_LENS, [Species.TORCHIC]: Abilities.RECKLESS, [Species.MUDKIP]: Abilities.DRIZZLE, @@ -3630,7 +3637,7 @@ export const starterPassiveAbilities = { [Species.PANPOUR]: Abilities.SAP_SIPPER, [Species.MUNNA]: Abilities.NEUTRALIZING_GAS, [Species.PIDOVE]: Abilities.SNIPER, - [Species.BLITZLE]: Abilities.RECKLESS, + [Species.BLITZLE]: Abilities.ELECTRIC_SURGE, [Species.ROGGENROLA]: Abilities.SOLID_ROCK, [Species.WOOBAT]: Abilities.OPPORTUNIST, [Species.DRILBUR]: Abilities.SAND_STREAM, @@ -3830,7 +3837,7 @@ export const starterPassiveAbilities = { [Species.DURALUDON]: Abilities.STEELWORKER, [Species.DREEPY]: Abilities.PARENTAL_BOND, [Species.ZACIAN]: Abilities.UNNERVE, - [Species.ZAMAZENTA]: Abilities.STAMINA, + [Species.ZAMAZENTA]: Abilities.UNNERVE, [Species.ETERNATUS]: Abilities.NEUTRALIZING_GAS, [Species.KUBFU]: Abilities.IRON_FIST, [Species.ZARUDE]: Abilities.TOUGH_CLAWS, @@ -3862,7 +3869,7 @@ export const starterPassiveAbilities = { [Species.KLAWF]: Abilities.WATER_ABSORB, [Species.CAPSAKID]: Abilities.PARENTAL_BOND, [Species.RELLOR]: Abilities.PRANKSTER, - [Species.FLITTLE]: Abilities.MAGIC_BOUNCE, + [Species.FLITTLE]: Abilities.DAZZLING, [Species.TINKATINK]: Abilities.STEELWORKER, [Species.WIGLETT]: Abilities.STURDY, [Species.BOMBIRDIER]: Abilities.UNBURDEN, @@ -3913,7 +3920,7 @@ export const starterPassiveAbilities = { [Species.TERAPAGOS]: Abilities.SOUL_HEART, [Species.PECHARUNT]: Abilities.TOXIC_CHAIN, [Species.ALOLA_RATTATA]: Abilities.ADAPTABILITY, - [Species.ALOLA_SANDSHREW]: Abilities.TOUGH_CLAWS, + [Species.ALOLA_SANDSHREW]: Abilities.ICE_SCALES, [Species.ALOLA_VULPIX]: Abilities.SHEER_FORCE, [Species.ALOLA_DIGLETT]: Abilities.STURDY, [Species.ALOLA_MEOWTH]: Abilities.DARK_AURA, diff --git a/src/data/splash-messages.ts b/src/data/splash-messages.ts index 8e95bba0591..b8069f77737 100644 --- a/src/data/splash-messages.ts +++ b/src/data/splash-messages.ts @@ -1,46 +1,136 @@ -import i18next from "i18next"; +import { USE_SEASONAL_SPLASH_MESSAGES } from "#app/constants"; -export function getBattleCountSplashMessage(): string { - return `{COUNT} ${i18next.t("splashMessages:battlesWon")}`; +//#region Interfaces/Types + +type Month = "01" | "02" | "03" | "04" | "05" | "06" | "07" | "08" | "09" | "10" | "11" | "12"; +type Day = + | Month + | "13" + | "14" + | "15" + | "16" + | "17" + | "18" + | "19" + | "20" + | "21" + | "22" + | "23" + | "24" + | "25" + | "26" + | "27" + | "28" + | "29" + | "30" + | "31"; + +/** + * Represents a season with its {@linkcode name}, + * {@linkcode start} day+month, {@linkcode end} day+month + * and {@linkcode messages}. + */ +interface Season { + /** The name of the season (internal use only) */ + name: string; + /** The start day and month of the season. Format `MM-DD` */ + start: `${Month}-${Day}`; + /** The end day and month of the season. Format `MM-DD` */ + end: `${Month}-${Day}`; + /** Collection of the messages to display (without the `i18next.t()` call!) */ + messages: string[]; } +//#region Constants + +/** The weight multiplier for the battles-won splash message */ +const BATTLES_WON_WEIGHT_MULTIPLIER = 10; +/** The weight multiplier for the seasonal splash messages */ +const SEASONAL_WEIGHT_MULTIPLIER = 10; + +//#region Common Messages + +const commonSplashMessages = [ + ...Array(BATTLES_WON_WEIGHT_MULTIPLIER).fill("battlesWon"), + "joinTheDiscord", + "infiniteLevels", + "everythingStacks", + "optionalSaveScumming", + "biomes", + "openSource", + "playWithSpeed", + "liveBugTesting", + "heavyInfluence", + "pokemonRiskAndPokemonRain", + "nowWithMoreSalt", + "infiniteFusionAtHome", + "brokenEggMoves", + "magnificent", + "mubstitute", + "thatsCrazy", + "oranceJuice", + "questionableBalancing", + "coolShaders", + "aiFree", + "suddenDifficultySpikes", + "basedOnAnUnfinishedFlashGame", + "moreAddictiveThanIntended", + "mostlyConsistentSeeds", + "achievementPointsDontDoAnything", + "youDoNotStartAtLevel", + "dontTalkAboutTheManaphyEggIncident", + "alsoTryPokengine", + "alsoTryEmeraldRogue", + "alsoTryRadicalRed", + "eeveeExpo", + "ynoproject", + "breedersInSpace", +]; + +//#region Seasonal Messages + +const seasonalSplashMessages: Season[] = [ + { + name: "Halloween", + start: "09-15", + end: "10-31", + messages: ["halloween.pumpkaboosAbout", "halloween.mayContainSpiders", "halloween.spookyScaryDuskulls"], + }, + { + name: "XMAS", + start: "12-01", + end: "12-26", + messages: ["xmas.happyHolidays", "xmas.delibirdSeason"], + }, + { + name: "New Year's", + start: "01-01", + end: "01-31", + messages: ["newYears.happyNewYear"], + }, +]; + +//#endregion + export function getSplashMessages(): string[] { - const splashMessages = Array(10).fill(getBattleCountSplashMessage()); - splashMessages.push( - i18next.t("splashMessages:joinTheDiscord"), - i18next.t("splashMessages:infiniteLevels"), - i18next.t("splashMessages:everythingStacks"), - i18next.t("splashMessages:optionalSaveScumming"), - i18next.t("splashMessages:biomes"), - i18next.t("splashMessages:openSource"), - i18next.t("splashMessages:playWithSpeed"), - i18next.t("splashMessages:liveBugTesting"), - i18next.t("splashMessages:heavyInfluence"), - i18next.t("splashMessages:pokemonRiskAndPokemonRain"), - i18next.t("splashMessages:nowWithMoreSalt"), - i18next.t("splashMessages:infiniteFusionAtHome"), - i18next.t("splashMessages:brokenEggMoves"), - i18next.t("splashMessages:magnificent"), - i18next.t("splashMessages:mubstitute"), - i18next.t("splashMessages:thatsCrazy"), - i18next.t("splashMessages:oranceJuice"), - i18next.t("splashMessages:questionableBalancing"), - i18next.t("splashMessages:coolShaders"), - i18next.t("splashMessages:aiFree"), - i18next.t("splashMessages:suddenDifficultySpikes"), - i18next.t("splashMessages:basedOnAnUnfinishedFlashGame"), - i18next.t("splashMessages:moreAddictiveThanIntended"), - i18next.t("splashMessages:mostlyConsistentSeeds"), - i18next.t("splashMessages:achievementPointsDontDoAnything"), - i18next.t("splashMessages:youDoNotStartAtLevel"), - i18next.t("splashMessages:dontTalkAboutTheManaphyEggIncident"), - i18next.t("splashMessages:alsoTryPokengine"), - i18next.t("splashMessages:alsoTryEmeraldRogue"), - i18next.t("splashMessages:alsoTryRadicalRed"), - i18next.t("splashMessages:eeveeExpo"), - i18next.t("splashMessages:ynoproject"), - i18next.t("splashMessages:breedersInSpace"), - ); + const splashMessages: string[] = [...commonSplashMessages]; + console.log("use seasonal splash messages", USE_SEASONAL_SPLASH_MESSAGES); + if (USE_SEASONAL_SPLASH_MESSAGES) { + // add seasonal splash messages if the season is active + for (const { name, start, end, messages } of seasonalSplashMessages) { + const now = new Date(); + const startDate = new Date(`${start}-${now.getFullYear()}`); + const endDate = new Date(`${end}-${now.getFullYear()}`); - return splashMessages; + if (now >= startDate && now <= endDate) { + console.log(`Adding ${messages.length} ${name} splash messages (weight: x${SEASONAL_WEIGHT_MULTIPLIER})`); + messages.forEach((message) => { + const weightedMessage = Array(SEASONAL_WEIGHT_MULTIPLIER).fill(message); + splashMessages.push(...weightedMessage); + }); + } + } + } + + return splashMessages.map((message) => `splashMessages:${message}`); } diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index 8b96e3cefb8..63ea1ed463d 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -1,6 +1,6 @@ import BattleScene, { startingWave } from "../battle-scene"; import { ModifierTypeFunc, modifierTypes } from "../modifier/modifier-type"; -import { EnemyPokemon } from "../field/pokemon"; +import { EnemyPokemon, PokemonMove } from "../field/pokemon"; import * as Utils from "../utils"; import { PokeballType } from "./pokeball"; import { pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions"; @@ -335,6 +335,9 @@ export class TrainerConfig { case TrainerType.ROSE_2: trainerType = TrainerType.ROSE; break; + case TrainerType.PENNY_2: + trainerType = TrainerType.PENNY; + break; case TrainerType.MARNIE_ELITE: trainerType = TrainerType.MARNIE; break; @@ -619,6 +622,41 @@ export class TrainerConfig { [TrainerPoolTier.RARE]: [Species.TINKATINK, Species.HISUI_LILLIGANT] }; } + case "star_1": { + return { + [TrainerPoolTier.COMMON]: [ Species.MURKROW, Species.SEEDOT, Species.CACNEA, Species.STUNKY, Species.SANDILE, Species.NYMBLE, Species.MASCHIFF, Species.GALAR_ZIGZAGOON ], + [TrainerPoolTier.UNCOMMON]: [ Species.UMBREON, Species.SNEASEL, Species.CORPHISH, Species.ZORUA, Species.INKAY, Species.BOMBIRDIER ], + [TrainerPoolTier.RARE]: [ Species.DEINO, Species.SPRIGATITO ] + }; + } + case "star_2": { + return { + [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.HOUNDOUR, Species.NUMEL, Species.LITWICK, Species.FLETCHLING, Species.LITLEO, Species.ROLYCOLY, Species.CAPSAKID ], + [TrainerPoolTier.UNCOMMON]: [ Species.PONYTA, Species.FLAREON, Species.MAGBY, Species.TORKOAL, Species.SALANDIT, Species.TURTONATOR ], + [TrainerPoolTier.RARE]: [ Species.LARVESTA, Species.FUECOCO ] + }; + } + case "star_3": { + return { + [TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ], + [TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ], + [TrainerPoolTier.RARE]: [ Species.BULBASAUR, Species.GLIMMET ] + }; + } + case "star_4": { + return { + [TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ], + [TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ], + [TrainerPoolTier.RARE]: [ Species.POPPLIO, Species.GALAR_PONYTA ] + }; + } + case "star_5": { + return { + [TrainerPoolTier.COMMON]: [ Species.SHROOMISH, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.SCRAGGY, Species.MIENFOO, Species.PAWMI, Species.PALDEA_TAUROS ], + [TrainerPoolTier.UNCOMMON]: [ Species.RIOLU, Species.TIMBURR, Species.HAWLUCHA, Species.PASSIMIAN, Species.FALINKS, Species.FLAMIGO ], + [TrainerPoolTier.RARE]: [ Species.JANGMO_O, Species.QUAXLY ] + }; + } } console.warn(`Evil team admin for ${team} not found. Returning empty species pools.`); @@ -911,7 +949,7 @@ export class TrainerConfig { if (!getIsInitialized()) { initI18n(); } - this.name = i18next.t(`trainerNames:${name.toLowerCase()}`); + this.name = i18next.t(`trainerNames:${name.toLowerCase().replace(/\s/g, "_")}`); return this; } @@ -1105,8 +1143,16 @@ function getGymLeaderPartyTemplate(scene: BattleScene) { return getWavePartyTemplate(scene, trainerPartyTemplates.GYM_LEADER_1, trainerPartyTemplates.GYM_LEADER_2, trainerPartyTemplates.GYM_LEADER_3, trainerPartyTemplates.GYM_LEADER_4, trainerPartyTemplates.GYM_LEADER_5); } -function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: TrainerSlot = TrainerSlot.TRAINER, ignoreEvolution: boolean = false, postProcess?: (enemyPokemon: EnemyPokemon) => void): PartyMemberFunc { - return (scene: BattleScene, level: integer, strength: PartyMemberStrength) => { +/** + * Randomly selects one of the `Species` from `speciesPool`, determines its evolution, level, and strength. + * Then adds Pokemon to scene. + * @param speciesPool + * @param trainerSlot + * @param ignoreEvolution + * @param postProcess + */ +export function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: TrainerSlot = TrainerSlot.TRAINER, ignoreEvolution: boolean = false, postProcess?: (enemyPokemon: EnemyPokemon) => void) { + return (scene: BattleScene, level: number, strength: PartyMemberStrength) => { let species = Utils.randSeedItem(speciesPool); if (!ignoreEvolution) { species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex); @@ -1270,7 +1316,7 @@ export const signatureSpecies: SignatureSpecies = { IRIS: [Species.HAXORUS, Species.RESHIRAM, Species.ARCHEOPS], // Druddigon lead, Gmax Lapras DIANTHA: [Species.HAWLUCHA, Species.XERNEAS, Species.GOODRA], // Gourgeist lead, Mega Gardevoir HAU: [[Species.SOLGALEO, Species.LUNALA], Species.NOIVERN, [Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA], [Species.TAPU_BULU, Species.TAPU_FINI, Species.TAPU_KOKO, Species.TAPU_LELE]], // Alola Raichu lead - LEON: [Species.DRAGAPULT, [Species.ZACIAN, Species.ZAMAZENTA], Species.AEGISLASH], // Rillaboom/Cinderace/Inteleon lead, GMax Charizard + LEON: [Species.DRAGAPULT, Species.ZACIAN, Species.AEGISLASH], // Rillaboom/Cinderace/Inteleon lead, GMax Charizard GEETA: [Species.MIRAIDON, [Species.ESPATHRA, Species.VELUZA], [Species.AVALUGG, Species.HISUI_AVALUGG], Species.KINGAMBIT], // Glimmora lead NEMONA: [Species.KORAIDON, Species.PAWMOT, [Species.DUDUNSPARCE, Species.ORTHWORM], [Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL]], // Lycanroc lead KIERAN: [[Species.GRIMMSNARL, Species.INCINEROAR, Species.PORYGON_Z], Species.OGERPON, Species.TERAPAGOS, Species.HYDRAPPLE], // Poliwrath/Politoed lead @@ -1520,6 +1566,38 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [Species.DURALUDON, Species.DREEPY] }), [TrainerType.OLEANA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("macro_admin", "macro", [Species.GARBODOR]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_oleana").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), + [TrainerType.STAR_GRUNT]: new TrainerConfig(++t).setHasGenders("Star Grunt Female").setHasDouble("Star Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + .setSpeciesPools({ + [TrainerPoolTier.COMMON]: [ Species.DUNSPARCE, Species.HOUNDOUR, Species.AZURILL, Species.GULPIN, Species.FOONGUS, Species.FLETCHLING, Species.LITLEO, Species.FLABEBE, Species.CRABRAWLER, Species.NYMBLE, Species.PAWMI, Species.FIDOUGH, Species.SQUAWKABILLY, Species.MASCHIFF, Species.SHROODLE, Species.KLAWF, Species.WIGLETT, Species.PALDEA_WOOPER ], + [TrainerPoolTier.UNCOMMON]: [ Species.KOFFING, Species.EEVEE, Species.GIRAFARIG, Species.RALTS, Species.TORKOAL, Species.SEVIPER, Species.SCRAGGY, Species.ZORUA, Species.MIMIKYU, Species.IMPIDIMP, Species.FALINKS, Species.CAPSAKID, Species.TINKATINK, Species.BOMBIRDIER, Species.CYCLIZAR, Species.FLAMIGO, Species.PALDEA_TAUROS ], + [TrainerPoolTier.RARE]: [ Species.MANKEY, Species.PAWNIARD, Species.CHARCADET, Species.FLITTLE, Species.VAROOM, Species.ORTHWORM], + [TrainerPoolTier.SUPER_RARE]: [ Species.DONDOZO, Species.GIMMIGHOUL ] + }), + [TrainerType.GIACOMO]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_1", [Species.KINGAMBIT]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([Species.REVAVROOM], TrainerSlot.TRAINER, true, p => { + p.formIndex = 1; // Segin Starmobile + p.moveset = [ new PokemonMove(Moves.WICKED_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ]; + })), + [TrainerType.MELA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_2", [Species.ARMAROUGE]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([Species.REVAVROOM], TrainerSlot.TRAINER, true, p => { + p.formIndex = 2; // Schedar Starmobile + p.moveset = [ new PokemonMove(Moves.BLAZING_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ]; + })), + [TrainerType.ATTICUS]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_3", [Species.REVAVROOM]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([Species.REVAVROOM], TrainerSlot.TRAINER, true, p => { + p.formIndex = 3; // Navi Starmobile + p.moveset = [ new PokemonMove(Moves.NOXIOUS_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ]; + })), + [TrainerType.ORTEGA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_4", [Species.DACHSBUN]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([Species.REVAVROOM], TrainerSlot.TRAINER, true, p => { + p.formIndex = 4; // Ruchbah Starmobile + p.moveset = [ new PokemonMove(Moves.MAGICAL_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ]; + })), + [TrainerType.ERI]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_5", [Species.ANNIHILAPE]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([Species.REVAVROOM], TrainerSlot.TRAINER, true, p => { + p.formIndex = 5; // Caph Starmobile + p.moveset = [ new PokemonMove(Moves.COMBAT_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ]; + })), [TrainerType.BROCK]: new TrainerConfig((t = TrainerType.BROCK)).initForGymLeader(signatureSpecies["BROCK"], true, Type.ROCK).setBattleBgm("battle_kanto_gym").setMixedBattleBgm("battle_kanto_gym"), [TrainerType.MISTY]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["MISTY"], false, Type.WATER).setBattleBgm("battle_kanto_gym").setMixedBattleBgm("battle_kanto_gym"), @@ -2154,6 +2232,64 @@ export const trainerConfigs: TrainerConfigs = { p.generateName(); p.pokeball = PokeballType.ULTRA_BALL; })), + [TrainerType.PENNY]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", []).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma") + .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VAPOREON, Species.JOLTEON, Species.FLAREON ])) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ESPEON, Species.UMBREON ], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 2; // Magic Bounce Espeon, Inner Focus Umbreon + p.generateAndPopulateMoveset(); + })) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.LEAFEON, Species.GLACEON ])) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.ROTOM ], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.formIndex = Utils.randSeedInt(5, 1); // Heat, Wash, Frost, Fan, or Mow + })) + .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.abilityIndex = 2; // Pixilate + })) + .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => { + p.setBoss(true, 2); + p.generateAndPopulateMoveset(); + p.formIndex = 2; // G-Max Eevee + p.pokeball = PokeballType.ULTRA_BALL; + p.generateName(); + })) + .setGenModifiersFunc(party => { + const teraPokemon = party[4]; + return [modifierTypes.TERA_SHARD().generateType([], [teraPokemon.species.type1])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier]; //TODO: is the bang correct? + }), + [TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma") + .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => { + p.setBoss(true, 2); + p.generateAndPopulateMoveset(); + p.formIndex = Utils.randSeedInt(5, 1); //Random Starmobile form + p.pokeball = PokeballType.ULTRA_BALL; + })) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ENTEI, Species.RAIKOU, Species.SUICUNE ], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.pokeball = PokeballType.ULTRA_BALL; + })) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.WALKING_WAKE, Species.GOUGING_FIRE, Species.RAGING_BOLT ])) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.abilityIndex = 2; // Pixilate + })) + .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => { + p.setBoss(true, 2); + p.generateAndPopulateMoveset(); + p.formIndex = 2; + p.generateName(); + p.pokeball = PokeballType.ULTRA_BALL; + })) + .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.ZAMAZENTA ], TrainerSlot.TRAINER, true, p => { + p.setBoss(true, 2); + p.generateAndPopulateMoveset(); + p.pokeball = PokeballType.MASTER_BALL; + })) + .setGenModifiersFunc(party => { + const teraPokemon = party[3]; + return [modifierTypes.TERA_SHARD().generateType([], [teraPokemon.species.type1])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier]; //TODO: is the bang correct? + }), [TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLAYDOL ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 3); @@ -2302,6 +2438,8 @@ export const trainerConfigs: TrainerConfigs = { .setMoneyMultiplier(2) .setPartyTemplates(new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(2, PartyMemberStrength.STRONG))), [TrainerType.BUG_TYPE_SUPERFAN]: new TrainerConfig(++t).setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.ACE_TRAINER) - .setPartyTemplates(new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE)) + .setPartyTemplates(new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE)), + [TrainerType.EXPERT_POKEMON_BREEDER]: new TrainerConfig(++t).setMoneyMultiplier(3).setEncounterBgm(TrainerType.ACE_TRAINER).setLocalizedName("Expert Pokemon Breeder") + .setPartyTemplates(new TrainerPartyTemplate(3, PartyMemberStrength.STRONG)) }; diff --git a/src/enums/mystery-encounter-type.ts b/src/enums/mystery-encounter-type.ts index 39a8087599c..b973652b113 100644 --- a/src/enums/mystery-encounter-type.ts +++ b/src/enums/mystery-encounter-type.ts @@ -28,5 +28,6 @@ export enum MysteryEncounterType { BUG_TYPE_SUPERFAN, FUN_AND_GAMES, UNCOMMON_BREED, - GLOBAL_TRADE_SYSTEM + GLOBAL_TRADE_SYSTEM, + THE_EXPERT_POKEMON_BREEDER } diff --git a/src/enums/trainer-type.ts b/src/enums/trainer-type.ts index cfc52b70eb0..cb7509067b5 100644 --- a/src/enums/trainer-type.ts +++ b/src/enums/trainer-type.ts @@ -78,6 +78,12 @@ export enum TrainerType { PLUMERIA, MACRO_GRUNT, OLEANA, + STAR_GRUNT, + GIACOMO, + MELA, + ATTICUS, + ORTEGA, + ERI, ROCKET_BOSS_GIOVANNI_1, ROCKET_BOSS_GIOVANNI_2, MAXIE, @@ -96,6 +102,8 @@ export enum TrainerType { GUZMA_2, ROSE, ROSE_2, + PENNY, + PENNY_2, BUCK, CHERYL, MARLEY, @@ -107,6 +115,7 @@ export enum TrainerType { VICKY, VITO, BUG_TYPE_SUPERFAN, + EXPERT_POKEMON_BREEDER, BROCK = 200, MISTY, diff --git a/src/field/arena.ts b/src/field/arena.ts index bf4075e5c1c..9897da7cfd7 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -1,19 +1,19 @@ import BattleScene from "../battle-scene"; -import { BiomePoolTier, PokemonPools, BiomeTierTrainerPools, biomePokemonPools, biomeTrainerPools } from "../data/biomes"; +import { biomePokemonPools, BiomePoolTier, BiomeTierTrainerPools, biomeTrainerPools, PokemonPools } from "../data/biomes"; import { Constructor } from "#app/utils"; import * as Utils from "../utils"; import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species"; -import { Weather, WeatherType, getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage } from "../data/weather"; +import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, Weather, WeatherType } from "../data/weather"; import { CommonAnim } from "../data/battle-anims"; import { Type } from "../data/type"; import Move from "../data/move"; import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "../data/arena-tag"; import { BattlerIndex } from "../battle"; import { Terrain, TerrainType } from "../data/terrain"; -import { PostTerrainChangeAbAttr, PostWeatherChangeAbAttr, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs } from "../data/ability"; +import { applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs, PostTerrainChangeAbAttr, PostWeatherChangeAbAttr } from "../data/ability"; import Pokemon from "./pokemon"; import Overrides from "#app/overrides"; -import { WeatherChangedEvent, TerrainChangedEvent, TagAddedEvent, TagRemovedEvent } from "../events/arena"; +import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "../events/arena"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; @@ -762,7 +762,7 @@ export class Arena { case Biome.BEACH: return 3.462; case Biome.LAKE: - return 5.350; + return 7.215; case Biome.SEABED: return 2.600; case Biome.MOUNTAIN: @@ -788,7 +788,7 @@ export class Arena { case Biome.FACTORY: return 4.985; case Biome.RUINS: - return 2.270; + return 0.000; case Biome.WASTELAND: return 6.336; case Biome.ABYSS: diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index cdafc960382..52cd995f473 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3,7 +3,7 @@ import BattleScene, { AnySound } from "../battle-scene"; import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; import { variantData } from "#app/data/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; -import Move, { HighCritAttr, DealsDoubleDamageToTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "../data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "../utils"; @@ -99,7 +99,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public luck: integer; public pauseEvolutions: boolean; public pokerus: boolean; - public wildFlee: boolean; + public switchOutStatus: boolean; public evoCounter: integer; public fusionSpecies: PokemonSpecies | null; @@ -145,7 +145,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.species = species; this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL; this.level = level; - this.wildFlee = false; + this.switchOutStatus = false; // Determine the ability index if (abilityIndex !== undefined) { @@ -343,7 +343,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { isAllowed(): boolean { const challengeAllowed = new Utils.BooleanHolder(true); applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed); - return !this.wildFlee && challengeAllowed.value; + return !this.isFainted() && challengeAllowed.value; } isActive(onField?: boolean): boolean { @@ -584,7 +584,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getSpriteScale(): number { const formKey = this.getFormKey(); - if (formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 || formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1) { + if (this.isMax() === true || formKey === "segin-starmobile" || formKey === "schedar-starmobile" || formKey === "navi-starmobile" || formKey === "ruchbah-starmobile" || formKey === "caph-starmobile") { return 1.5; } else if (this.mysteryEncounterPokemonData.spriteScale > 0) { return this.mysteryEncounterPokemonData.spriteScale; @@ -1513,7 +1513,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType); for (const tag of immuneTags) { - if (move && !move.getAttrs(DealsDoubleDamageToTagAttr).some(attr => attr.tagType === tag.tagType)) { + if (move && !move.getAttrs(HitsTagAttr).some(attr => attr.tagType === tag.tagType)) { typeMultiplier.value = 0; break; } @@ -2152,11 +2152,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * sets if the pokemon has fled (implies it's a wild pokemon) + * sets if the pokemon is switching out (if it's a enemy wild implies it's going to flee) * @param status - boolean */ - setWildFlee(status: boolean): void { - this.wildFlee = status; + setSwitchOutStatus(status: boolean): void { + this.switchOutStatus = status; } updateInfo(instant?: boolean): Promise { @@ -2322,11 +2322,61 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return accuracyMultiplier.value / evasionMultiplier.value; } + /** + * Calculates the base damage of the given move against this Pokemon when attacked by the given source. + * Used during damage calculation and for Shell Side Arm's forecasting effect. + * @param source the attacking {@linkcode Pokemon}. + * @param move the {@linkcode Move} used in the attack. + * @param moveCategory the move's {@linkcode MoveCategory} after variable-category effects are applied. + * @param ignoreAbility if `true`, ignores this Pokemon's defensive ability effects (defaults to `false`). + * @param ignoreSourceAbility if `true`, ignore's the attacking Pokemon's ability effects (defaults to `false`). + * @param isCritical if `true`, calculates effective stats as if the hit were critical (defaults to `false`). + * @param simulated if `true`, suppresses changes to game state during calculation (defaults to `true`). + * @returns The move's base damage against this Pokemon when used by the source Pokemon. + */ + getBaseDamage(source: Pokemon, move: Move, moveCategory: MoveCategory, ignoreAbility: boolean = false, ignoreSourceAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): number { + const isPhysical = moveCategory === MoveCategory.PHYSICAL; + + /** A base damage multiplier based on the source's level */ + const levelMultiplier = (2 * source.level / 5 + 2); + + /** The power of the move after power boosts from abilities, etc. have applied */ + const power = move.calculateBattlePower(source, this, simulated); + + /** + * The attacker's offensive stat for the given move's category. + * Critical hits cause negative stat stages to be ignored. + */ + const sourceAtk = new Utils.NumberHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, ignoreSourceAbility, ignoreAbility, isCritical, simulated)); + applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk); + + /** + * This Pokemon's defensive stat for the given move's category. + * Critical hits cause positive stat stages to be ignored. + */ + const targetDef = new Utils.NumberHolder(this.getEffectiveStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, ignoreAbility, ignoreSourceAbility, isCritical, simulated)); + applyMoveAttrs(VariableDefAttr, source, this, move, targetDef); + + /** + * The attack's base damage, as determined by the source's level, move power + * and Attack stat as well as this Pokemon's Defense stat + */ + const baseDamage = ((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2; + + /** Debug message for non-simulated calls (i.e. when damage is actually dealt) */ + if (!simulated) { + console.log("base damage", baseDamage, move.name, power, sourceAtk.value, targetDef.value); + } + + return baseDamage; + } + /** * Calculates the damage of an attack made by another Pokemon against this Pokemon * @param source {@linkcode Pokemon} the attacking Pokemon * @param move {@linkcode Pokemon} the move used in the attack * @param ignoreAbility If `true`, ignores this Pokemon's defensive ability effects + * @param ignoreSourceAbility If `true`, ignores the attacking Pokemon's ability effects * @param isCritical If `true`, calculates damage for a critical hit. * @param simulated If `true`, suppresses changes to game state during the calculation. * @returns a {@linkcode DamageCalculationResult} object with three fields: @@ -2395,35 +2445,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }; } - // ----- BEGIN BASE DAMAGE MULTIPLIERS ----- - - /** A base damage multiplier based on the source's level */ - const levelMultiplier = (2 * source.level / 5 + 2); - - /** The power of the move after power boosts from abilities, etc. have applied */ - const power = move.calculateBattlePower(source, this, simulated); - - /** - * The attacker's offensive stat for the given move's category. - * Critical hits ignore negative stat stages. - */ - const sourceAtk = new Utils.NumberHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, ignoreSourceAbility, ignoreAbility, isCritical, simulated)); - applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk); - - /** - * This Pokemon's defensive stat for the given move's category. - * Critical hits ignore positive stat stages. - */ - const targetDef = new Utils.NumberHolder(this.getEffectiveStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, ignoreAbility, ignoreSourceAbility, isCritical, simulated)); - applyMoveAttrs(VariableDefAttr, source, this, move, targetDef); - /** * The attack's base damage, as determined by the source's level, move power * and Attack stat as well as this Pokemon's Defense stat */ - const baseDamage = ((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2; - - // ------ END BASE DAMAGE MULTIPLIERS ------ + const baseDamage = this.getBaseDamage(source, move, moveCategory, ignoreAbility, ignoreSourceAbility, isCritical, simulated); /** 25% damage debuff on moves hitting more than one non-fainted target (regardless of immunities) */ const { targets, multiple } = getMoveTargets(source, move.id); @@ -2489,13 +2515,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier); /** - * For each {@linkcode DealsDoubleDamageToTagAttr} the move has, doubles the damage of the move if: + * For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if: * The target has a {@linkcode BattlerTagType} that this move interacts with * AND * The move doubles damage when used against that tag */ const hitsTagMultiplier = new Utils.NumberHolder(1); - move.getAttrs(DealsDoubleDamageToTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { + move.getAttrs(HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { if (this.getTag(hta.tagType)) { hitsTagMultiplier.value *= 2; } @@ -2549,7 +2575,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // debug message for when damage is applied (i.e. not simulated) if (!simulated) { - console.log("damage", damage.value, move.name, power, sourceAtk, targetDef); + console.log("damage", damage.value, move.name); } let hitResult: HitResult; @@ -3358,6 +3384,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.updateFusionPalette(); } this.summonData = new PokemonSummonData(); + this.setSwitchOutStatus(false); if (!this.battleData) { this.resetBattleData(); } @@ -3763,6 +3790,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.hideInfo(); } this.scene.field.remove(this); + this.setSwitchOutStatus(true); this.scene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true); } @@ -3786,6 +3814,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const rootForm = getPokemonSpecies(this.species.getRootSpeciesId()); return rootForm.getAbility(abilityIndex) === rootForm.getAbility(currentAbilityIndex); } + + /** + * Helper function to check if the player already owns the starter data of the Pokemon's + * current ability + * @param ownedAbilityAttrs the owned abilityAttr of this Pokemon's root form + * @returns true if the player already has it, false otherwise + */ + checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs: number): boolean { + if ((ownedAbilityAttrs & 1) > 0 && this.hasSameAbilityInRootForm(0)) { + return true; + } + if ((ownedAbilityAttrs & 2) > 0 && this.hasSameAbilityInRootForm(1)) { + return true; + } + if ((ownedAbilityAttrs & 4) > 0 && this.hasSameAbilityInRootForm(2)) { + return true; + } + return false; + } } export default interface Pokemon { @@ -4702,8 +4749,15 @@ export class EnemyPokemon extends Pokemon { return true; } + /** + * Go through a boss' health segments and give stats boosts for each newly cleared segment + * The base boost is 1 to a random stat that's not already maxed out per broken shield + * For Pokemon with 3 health segments or more, breaking the last shield gives +2 instead + * 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 { - while (segmentIndex - 1 < this.bossSegmentIndex) { + 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); const statWeights = leftoverStats.map((s: EffectiveStat) => this.getStat(s, false)); diff --git a/src/game-mode.ts b/src/game-mode.ts index a2d61d7cfff..525c975a19b 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -268,7 +268,6 @@ export class GameMode implements GameModeConfig { isFixedBattle(waveIndex: integer): boolean { const dummyConfig = new FixedBattleConfig(); return this.battleConfig.hasOwnProperty(waveIndex) || applyChallenges(this, ChallengeType.FIXED_BATTLES, waveIndex, dummyConfig); - } /** diff --git a/src/locales/de/move.json b/src/locales/de/move.json index 3c81ccfd7df..b7a42cb1787 100644 --- a/src/locales/de/move.json +++ b/src/locales/de/move.json @@ -3121,11 +3121,11 @@ }, "behemothBlade": { "name": "Gigantenhieb", - "effect": "Der Anwender wird zu einem riesigen Schwert und greift das Ziel an. Dynamaximierte Ziele erleiden doppelten Schaden." + "effect": "Der Anwender wird zu einem riesigen Schwert und greift das Ziel an." }, "behemothBash": { "name": "Gigantenstoß", - "effect": "Der Anwender wird zu einem riesigen Schild und greift das Ziel an. Dynamaximierte Ziele erleiden doppelten Schaden." + "effect": "Der Anwender wird zu einem riesigen Schild und greift das Ziel an." }, "auraWheel": { "name": "Aura-Rad", diff --git a/src/locales/de/splash-messages.json b/src/locales/de/splash-messages.json index ac3fd345f3f..ba126393ccb 100644 --- a/src/locales/de/splash-messages.json +++ b/src/locales/de/splash-messages.json @@ -1,5 +1,5 @@ { - "battlesWon": "Kämpfe gewonnen!", + "battlesWon": "{{count, number}} Kämpfe gewonnen!", "joinTheDiscord": "Tritt dem Discord bei!", "infiniteLevels": "Unendliche Level!", "everythingStacks": "Alles stapelt sich!", diff --git a/src/locales/en/achv.json b/src/locales/en/achv.json index 32d519fbf78..b04f23d4209 100644 --- a/src/locales/en/achv.json +++ b/src/locales/en/achv.json @@ -283,5 +283,9 @@ "INVERSE_BATTLE": { "name": "Mirror rorriM", "description": "Complete the Inverse Battle challenge.\n.egnellahc elttaB esrevnI eht etelpmoC" + }, + "BREEDERS_IN_SPACE": { + "name": "Breeders in Space!", + "description": "Beat the Expert Pokémon Breeder in the Space Biome." } } diff --git a/src/locales/en/bgm-name.json b/src/locales/en/bgm-name.json index e13f580def6..2da1b9e85a3 100644 --- a/src/locales/en/bgm-name.json +++ b/src/locales/en/bgm-name.json @@ -83,9 +83,11 @@ "battle_aether_grunt": "SM Aether Foundation Battle", "battle_skull_grunt": "SM Team Skull Battle", "battle_macro_grunt": "SWSH Trainer Battle", + "battle_star_grunt": "SV Team Star Battle", "battle_galactic_admin": "BDSP Team Galactic Admin Battle", "battle_skull_admin": "SM Team Skull Admin Battle", "battle_oleana": "SWSH Oleana Battle", + "battle_star_admin": "SV Team Star Boss", "battle_rocket_boss": "USUM Giovanni Battle", "battle_aqua_magma_boss": "ORAS Archie & Maxie Battle", "battle_galactic_boss": "BDSP Cyrus Battle", @@ -94,6 +96,7 @@ "battle_aether_boss": "SM Lusamine Battle", "battle_skull_boss": "SM Guzma Battle", "battle_macro_boss": "SWSH Rose Battle", + "battle_star_boss": "SV Cassiopeia Battle", "abyss": "PMD EoS Dark Crater", "badlands": "PMD EoS Barren Valley", @@ -112,13 +115,13 @@ "island": "PMD EoS Craggy Coast", "jungle": "Lmz - Jungle", "laboratory": "Firel - Laboratory", - "lake": "PMD EoS Crystal Cave", + "lake": "Lmz - Lake", "meadow": "PMD EoS Sky Peak Forest", "metropolis": "Firel - Metropolis", "mountain": "PMD EoS Mt. Horn", "plains": "Firel - Route 888", "power_plant": "Firel - The Klink", - "ruins": "PMD EoS Deep Sealed Ruin", + "ruins": "Lmz - Ancient Ruins", "sea": "Andr06 - Marine Mystique", "seabed": "Firel - Seabed", "slum": "Andr06 - Sneaky Snom", @@ -151,5 +154,6 @@ "mystery_encounter_weird_dream": "PMD EoS Temporal Spire", "mystery_encounter_fun_and_games": "PMD EoS Guildmaster Wigglytuff", "mystery_encounter_gen_5_gts": "BW GTS", - "mystery_encounter_gen_6_gts": "XY GTS" + "mystery_encounter_gen_6_gts": "XY GTS", + "mystery_encounter_delibirdy": "Firel - DeliDelivery!" } diff --git a/src/locales/en/config.ts b/src/locales/en/config.ts index f83fec5be26..35eef91e2ad 100644 --- a/src/locales/en/config.ts +++ b/src/locales/en/config.ts @@ -84,6 +84,7 @@ import bugTypeSuperfan from "#app/locales/en/mystery-encounters/bug-type-superfa import funAndGames from "#app/locales/en/mystery-encounters/fun-and-games-dialogue.json"; import uncommonBreed from "#app/locales/en/mystery-encounters/uncommon-breed-dialogue.json"; import globalTradeSystem from "#app/locales/en/mystery-encounters/global-trade-system-dialogue.json"; +import expertPokemonBreeder from "#app/locales/en/mystery-encounters/the-expert-pokemon-breeder-dialogue.json"; /** * Dialogue/Text token injection patterns that can be used: @@ -183,7 +184,8 @@ export const enConfig = { bugTypeSuperfan, funAndGames, uncommonBreed, - globalTradeSystem + globalTradeSystem, + expertPokemonBreeder }, mysteryEncounterMessages }; diff --git a/src/locales/en/dialogue.json b/src/locales/en/dialogue.json index 39a4238355c..66f9e8db7a5 100644 --- a/src/locales/en/dialogue.json +++ b/src/locales/en/dialogue.json @@ -764,12 +764,16 @@ "1": "It looks like this is the end of the line for you!", "2": "You are a trainer aren't you? I'm afraid that doesn't give you the right to interfere in our work.", "2_female": "You are a trainer aren't you? I'm afraid that doesn't give you the right to interfere in our work.", - "3": "I'm from Macro Cosmos Insurance! Do you have a life insurance policy?" + "3": "I'm from Macro Cosmos Insurance! Do you have a life insurance policy?", + "4": "I found you! In that case, time for a Pokémon battle!", + "5": "An earful from Ms. Oleana is way worse than anything you can do!" }, "victory": { "1": "I have little choice but to respectfully retreat.", "2": "Having to give up my pocket money... Losing means I'm back in the red...", - "3": "Nobody can beat Macro Cosmos when it comes to our dedication to our work!" + "3": "Nobody can beat Macro Cosmos when it comes to our dedication to our work!", + "4": "I even switched up my Pokémon...", + "5": "Battles didn't work... Only thing to do now is run!" } }, "oleana": { @@ -785,6 +789,73 @@ "3": "*sigh* I am one tired Oleana..." } }, + "star_grunt": { + "encounter": { + "1": "We're Team Star, kid. We burn so bright, it hurts to look at us!", + "2": "We'll come at you full force - Hasta la vistaaar! ☆", + "3": "If you don't clear out real quick-like, I'll hafta come at you in self-defense. You get me?", + "4": "Sorry, but if you don't turn yourself around here, amigo, we'll have to send you packing!", + "4_female": "Sorry, but if you don't turn yourself around here, amiga, we'll have to send you packing!", + "5": "Oh great. Here comes another rando to ruin my day." + }, + "victory": { + "1": "How come I'M the one seeing stars?!", + "2": "You're scary, kid. If you joined Team Star, you'd be looking down from the top in no time!", + "3": "I defended myself all right... But it wasn't enough!", + "4": "H-hasta la vistar... ☆", + "5": "I didn't think grunt work for Team Star newbies would be this much of a chore..." + } + }, + "giacomo": { + "encounter": { + "1": "You don't really think things through, do ya? Declarin' war on Team Star is a real bad move.", + "2": "I'll play you a sick requiem as you crash and burn. Let's get this party staaarteeed!" + }, + "victory": { + "1": "Guess that's that...", + "2": "You turned my melody into a threnody..." + } + }, + "mela": { + "encounter": { + "1": "So you're the dope who picked a fight with Team Star... Prepare to get messed up.", + "2": "All riiight, BRING IT! I'll blow everythin' sky high!" + }, + "victory": { + "1": "Ugh. Is this really how it's gonna end? What a hassle...", + "2": "I burned through everythin' I had...and now I've sputtered out." + } + }, + "atticus": { + "encounter": { + "1": "You have some nerve baring your fangs at Team Star. Come, then, villainous wretch!", + "2": "Be warned—I shall spare thee no mercy! En garde!" + }, + "victory": { + "1": "Forgive me, my friends...", + "2": "You have utterly bested me. But thy victory stir'd no bitterness within me—such was its brilliance." + } + }, + "ortega": { + "encounter": { + "1": "I promise I'll play nice, so don't blame me when this battle sends you blubbering back home!", + "2": "I'll wipe that smug look off your face for sure! You're going down!" + }, + "victory": { + "1": "Ugh! How could I LOSE! What the HECK!", + "2": "Arrrrgggh! That strength of yours is SO. NOT. FAIR." + } + }, + "eri": { + "encounter": { + "1": "Doesn't matter who you are. I'll bury anyone who tries to take down Team Star!", + "2": "I give as good as I get—that's a promise! We'll see who's left standing in the end!" + }, + "victory": { + "1": "I'm so sorry, everyone...", + "2": "I gave it my all, but it wasn't enough—I wasn't enough..." + } + }, "rocket_boss_giovanni_1": { "encounter": { "1": "So! I must say, I am impressed you got here!" @@ -985,6 +1056,28 @@ "1": "I suppose it must seem that I am doing something terrible. I don't expect you to understand.\n$But I must provide the Galar region with limitless energy to ensure everlasting prosperity." } }, + "star_boss_penny_1": { + "encounter": { + "1": "I'm the big boss of Team Star. The name's Cassiopeia. \n$Now, bow down before the overwhelming might of Team Star's founder!" + }, + "victory": { + "1": "... ... .." + }, + "defeat": { + "1": "Heh..." + } + }, + "star_boss_penny_2": { + "encounter": { + "1": "I won't hold back in this battle! I'll stay true to Team Star's code! \n$My Veevee power will crush you into stardust!" + }, + "victory": { + "1": "...It's all over now." + }, + "defeat": { + "1": "I can't fault you on your battle skills at all... Considering how the bosses fell at your hands." + } + }, "stat_trainer_buck": { "encounter": { "1": "...I'm telling you right now. I'm seriously tough. Act surprised!", diff --git a/src/locales/en/mystery-encounters/an-offer-you-cant-refuse-dialogue.json b/src/locales/en/mystery-encounters/an-offer-you-cant-refuse-dialogue.json index 6dd54d302ab..e286d89691a 100644 --- a/src/locales/en/mystery-encounters/an-offer-you-cant-refuse-dialogue.json +++ b/src/locales/en/mystery-encounters/an-offer-you-cant-refuse-dialogue.json @@ -1,7 +1,7 @@ { "intro": "You're stopped by a rich looking boy.", "speaker": "Rich Boy", - "intro_dialogue": "Good day to you.$I can't help but notice that your\n{{strongestPokemon}} looks positively divine!$I've always wanted to have a pet like that!$I'd pay you handsomely,\nand also give you this old bauble!", + "intro_dialogue": "Good day to you.$I can't help but notice that your\n{{strongestPokemon}} looks positively divine!$I've always wanted to have a Pokémon like that!$I'd pay you handsomely,\nand also give you this old bauble!", "title": "An Offer You Can't Refuse", "description": "You're being offered a @[TOOLTIP_TITLE]{Shiny Charm} and {{price, money}} for your {{strongestPokemon}}!\n\nIt's an extremely good deal, but can you really bear to part with such a strong team member?", "query": "What will you do?", diff --git a/src/locales/en/mystery-encounters/bug-type-superfan-dialogue.json b/src/locales/en/mystery-encounters/bug-type-superfan-dialogue.json index 7df01326aed..09488addb98 100644 --- a/src/locales/en/mystery-encounters/bug-type-superfan-dialogue.json +++ b/src/locales/en/mystery-encounters/bug-type-superfan-dialogue.json @@ -17,9 +17,9 @@ "disabled_tooltip": "You need at least 1 Bug Type Pokémon on your team to select this.", "selected": "You show the trainer all your Bug Type Pokémon...", "selected_0_to_1": "Huh? You only have {{numBugTypes}}...$Guess I'm wasting my breath on someone like you...", - "selected_2_to_3": "Hey, you've got {{numBugTypes}} Bug Types!\nNot bad.$Here, this might help you on your journey to catch more!", - "selected_4_to_5": "What? You have {{numBugTypes}} Bug Types?\nNice!$You're not quite at my level, but I can see shades of myself in you!\n$Take this, my young apprentice!", - "selected_6": "Whoa! {{numBugTypes}} Bug Types!\n$You must love Bug Types almost as much as I do!$Here, take this as a token of our camaraderie!" + "selected_2_to_3": "Hey, you've got {{numBugTypes}}!\nNot bad.$Here, this might help you on your journey to catch more!", + "selected_4_to_5": "What? You have {{numBugTypes}}?\nNice!$You're not quite at my level, but I can see shades of myself in you!\n$Take this, my young apprentice!", + "selected_6": "Whoa! {{numBugTypes}}!\n$You must love Bug Types almost as much as I do!$Here, take this as a token of our camaraderie!" }, "3": { "label": "Gift a Bug Item", @@ -34,5 +34,7 @@ "battle_won": "Your knowledge and skill were perfect at exploiting our weaknesses!$In exchange for the valuable lesson,\nallow me to teach one of your Pokémon a Bug Type Move!", "teach_move_prompt": "Select a move to teach a Pokémon.", "confirm_no_teach": "You sure you don't want to learn one of these great moves?", - "outro": "I see great Bug Pokémon in your future!\nMay our paths cross again!$Bug out!" + "outro": "I see great Bug Pokémon in your future!\nMay our paths cross again!$Bug out!", + "numBugTypes_one": "{{count}} Bug Type", + "numBugTypes_other": "{{count}} Bug Types" } \ No newline at end of file diff --git a/src/locales/en/mystery-encounters/part-timer-dialogue.json b/src/locales/en/mystery-encounters/part-timer-dialogue.json index 614f1818e3f..801a409ee84 100644 --- a/src/locales/en/mystery-encounters/part-timer-dialogue.json +++ b/src/locales/en/mystery-encounters/part-timer-dialogue.json @@ -21,7 +21,7 @@ "label": "Sales Assistant", "tooltip": "(-) Your {{option3PrimaryName}} uses {{option3PrimaryMove}}\n(+) Earn @[MONEY]{Money}", "disabled_tooltip": "Your Pokémon need to know certain moves for this job", - "selected": "Your {{option3PrimaryName}} spends the day using {{option3PrimaryMove}} to attract customers to the business!" + "selected": "Your {{option3PrimaryName}} spends the day using {{option3PrimaryMove}} to draw customers to the business!" } }, "job_complete_good": "Thanks for the assistance!\nYour {{selectedPokemon}} was incredibly helpful!$Here's your check for the day.", diff --git a/src/locales/en/mystery-encounters/the-expert-pokemon-breeder-dialogue.json b/src/locales/en/mystery-encounters/the-expert-pokemon-breeder-dialogue.json new file mode 100644 index 00000000000..ebe3af38add --- /dev/null +++ b/src/locales/en/mystery-encounters/the-expert-pokemon-breeder-dialogue.json @@ -0,0 +1,30 @@ +{ + "intro": "It's a trainer carrying tons of Pokémon Eggs!", + "intro_dialogue": "Hey there, trainer!$It looks like some of your\npartner Pokémon are feeling a little down.$Why not have a battle with me to cheer them up?", + "title": "The Expert Pokémon Breeder", + "description": "You've been challenged to a battle where @[TOOLTIP_TITLE]{you can only use a single Pokémon}. It might be tough, but it would surely deepen the bond you have with the Pokémon you choose!\nThe breeder will also give you some @[TOOLTIP_TITLE]{Pokémon Eggs} if you win!", + "query": "Who will you battle with?", + "cleffa_1_nickname": "Ace", + "cleffa_2_nickname": "Clefablest", + "cleffa_3_nickname": "{{speciesName}} the Great", + "option": { + "1": { + "label": "{{pokemon1Name}}", + "tooltip_base": "(-) Tough Battle\n(+) Gain Friendship with {{pokemon1Name}}" + }, + "2": { + "label": "{{pokemon2Name}}", + "tooltip_base": "(-) Tough Battle\n(+) Gain Friendship with {{pokemon2Name}}" + }, + "3": { + "label": "{{pokemon3Name}}", + "tooltip_base": "(-) Tough Battle\n(+) Gain Friendship with {{pokemon3Name}}" + }, + "selected": "Let's do this!" + }, + "outro": "Look how happy your {{chosenPokemon}} is now!$Here, you can have these as well.", + "gained_eggs": "@s{item_fanfare}You received {{numEggs}}!", + "eggs_tooltip": "\n(+) Earn {{eggs}}", + "numEggs_one": "{{count}} {{rarity}} Egg", + "numEggs_other": "{{count}} {{rarity}} Eggs" +} \ No newline at end of file diff --git a/src/locales/en/splash-messages.json b/src/locales/en/splash-messages.json index c0686e6ad75..168974525f8 100644 --- a/src/locales/en/splash-messages.json +++ b/src/locales/en/splash-messages.json @@ -1,5 +1,5 @@ { - "battlesWon": "Battles Won!", + "battlesWon": "{{count, number}} Battles Won!", "joinTheDiscord": "Join the Discord!", "infiniteLevels": "Infinite Levels!", "everythingStacks": "Everything Stacks!", @@ -32,5 +32,17 @@ "alsoTryRadicalRed": "Also Try Radical Red!", "eeveeExpo": "Eevee Expo!", "ynoproject": "YNOproject!", - "breedersInSpace": "Breeders in space!" + "breedersInSpace": "Breeders in space!", + "halloween": { + "pumpkaboosAbout": "Pumpkaboos about!", + "mayContainSpiders": "May contain spiders!", + "spookyScaryDuskulls": "Spooky, Scary Duskulls!" + }, + "xmas": { + "happyHolidays": "Happy Holidays!", + "delibirdSeason": "Delibird Season!" + }, + "newYears": { + "happyNewYear": "Happy New Year!" + } } \ No newline at end of file diff --git a/src/locales/en/trainer-classes.json b/src/locales/en/trainer-classes.json index 9e30915dee6..092bc8d895d 100644 --- a/src/locales/en/trainer-classes.json +++ b/src/locales/en/trainer-classes.json @@ -126,5 +126,8 @@ "skull_grunts": "Team Skull Grunts", "macro_grunt": "Macro Cosmos Trainer", "macro_grunt_female": "Macro Cosmos Trainer", - "macro_grunts": "Macro Cosmos Trainers" + "macro_grunts": "Macro Cosmos Trainers", + "star_grunt": "Star Grunt", + "star_grunt_female": "Star Grunt", + "star_grunts": "Star Grunts" } diff --git a/src/locales/en/trainer-names.json b/src/locales/en/trainer-names.json index 467ed03e044..1d51b48d43f 100644 --- a/src/locales/en/trainer-names.json +++ b/src/locales/en/trainer-names.json @@ -141,6 +141,11 @@ "faba": "Faba", "plumeria": "Plumeria", "oleana": "Oleana", + "giacomo": "Giacomo", + "mela": "Mela", + "atticus": "Atticus", + "ortega": "Ortega", + "eri": "Eri", "maxie": "Maxie", "archie": "Archie", @@ -150,6 +155,7 @@ "lusamine": "Lusamine", "guzma": "Guzma", "rose": "Rose", + "cassiopeia": "Cassiopeia", "blue_red_double": "Blue & Red", "red_blue_double": "Red & Blue", @@ -172,5 +178,6 @@ "vivi": "Vivi", "vicky": "Vicky", "vito": "Vito", - "bug_type_superfan": "Bug-Type Superfan" + "bug_type_superfan": "Bug-Type Superfan", + "expert_pokemon_breeder": "Expert Pokémon Breeder" } diff --git a/src/locales/en/trainer-titles.json b/src/locales/en/trainer-titles.json index 7ef715d115f..ae19fc30790 100644 --- a/src/locales/en/trainer-titles.json +++ b/src/locales/en/trainer-titles.json @@ -19,6 +19,7 @@ "aether_boss": "Aether President", "skull_boss": "Team Skull Boss", "macro_boss": "Macro Cosmos President", + "star_boss": "Team Star Leader", "rocket_admin": "Team Rocket Admin", "rocket_admin_female": "Team Rocket Admin", @@ -35,6 +36,7 @@ "aether_admin": "Aether Foundation Admin", "skull_admin": "Team Skull Admin", "macro_admin": "Macro Cosmos", + "star_admin": "Team Star Squad Boss", "the_winstrates": "The Winstrates'" } diff --git a/src/locales/es/splash-messages.json b/src/locales/es/splash-messages.json index b1d4820b06e..da31d394c0f 100644 --- a/src/locales/es/splash-messages.json +++ b/src/locales/es/splash-messages.json @@ -1,5 +1,5 @@ { - "battlesWon": "¡Batallas ganadas!", + "battlesWon": "¡{{count, number}} Batallas ganadas!", "joinTheDiscord": "¡Únete al Discord!", "infiniteLevels": "¡Niveles infinitos!", "everythingStacks": "¡Todo se acumula!", diff --git a/src/locales/fr/move.json b/src/locales/fr/move.json index 957895b5db9..da42f188a80 100644 --- a/src/locales/fr/move.json +++ b/src/locales/fr/move.json @@ -3121,11 +3121,11 @@ }, "behemothBlade": { "name": "Gladius Maximus", - "effect": "Le lanceur se transforme en une immense épée et pourfend sa cible. Cette capacité inflige le double de dégâts aux Pokémon Dynamax." + "effect": "Le lanceur se transforme en une immense épée et pourfend sa cible." }, "behemothBash": { "name": "Aegis Maxima", - "effect": "Le lanceur se transforme en un immense bouclier et charge sa cible. Cette capacité inflige le double de dégâts aux Pokémon Dynamax." + "effect": "Le lanceur se transforme en un immense bouclier et charge sa cible." }, "auraWheel": { "name": "Roue Libre", diff --git a/src/locales/fr/party-ui-handler.json b/src/locales/fr/party-ui-handler.json index a11640c80b3..4eef55da790 100644 --- a/src/locales/fr/party-ui-handler.json +++ b/src/locales/fr/party-ui-handler.json @@ -13,7 +13,7 @@ "ALL": "Tout", "PASS_BATON": "Relais", "UNPAUSE_EVOLUTION": "Réactiver Évolution", - "PAUSE_EVOLUTION": "Interrompre Évolution", + "PAUSE_EVOLUTION": "Empêcher Évolution", "REVIVE": "Ranimer", "RENAME": "Renommer", "choosePokemon": "Sélectionnez un Pokémon.", diff --git a/src/locales/fr/splash-messages.json b/src/locales/fr/splash-messages.json index 9dd3e86fb32..2ac85680e58 100644 --- a/src/locales/fr/splash-messages.json +++ b/src/locales/fr/splash-messages.json @@ -1,5 +1,5 @@ { - "battlesWon": "combats gagnés !", + "battlesWon": "{{count, number}} combats gagnés !", "joinTheDiscord": "Rejoins le Discord !", "infiniteLevels": "Niveaux infinis !", "everythingStacks": "Tout se cumule !", diff --git a/src/locales/it/splash-messages.json b/src/locales/it/splash-messages.json index 55018d0ada0..d4b411241b6 100644 --- a/src/locales/it/splash-messages.json +++ b/src/locales/it/splash-messages.json @@ -1,5 +1,5 @@ { - "battlesWon": "Battaglie Vinte!", + "battlesWon": "{{count, number}} Battaglie Vinte!", "joinTheDiscord": "Entra nel Discord!", "infiniteLevels": "Livelli Infiniti!", "everythingStacks": "Tutto si impila!", diff --git a/src/locales/ja/dialogue-final-boss.json b/src/locales/ja/dialogue-final-boss.json index f20d0f013d1..2378a09f6d6 100644 --- a/src/locales/ja/dialogue-final-boss.json +++ b/src/locales/ja/dialogue-final-boss.json @@ -1,10 +1,10 @@ { - "encounter": "It appears the time has finally come once again.\nYou know why you have come here, do you not?\n$You were drawn here, because you have been here before.\nCountless times.\n$Though, perhaps it can be counted.\nTo be precise, this is in fact your {{cycleCount}} cycle.\n$Each cycle your mind reverts to its former state.\nEven so, somehow, remnants of your former selves remain.\n$Until now you have yet to succeed, but I sense a different presence in you this time.\n\n$You are the only one here, though it is as if there is… another.\n$Will you finally prove a formidable challenge to me?\nThe challenge I have longed after for millennia?\n$We begin.", - "encounter_female": "It appears the time has finally come once again.\nYou know why you have come here, do you not?\n$You were drawn here, because you have been here before.\nCountless times.\n$Though, perhaps it can be counted.\nTo be precise, this is in fact your {{cycleCount}} cycle.\n$Each cycle your mind reverts to its former state.\nEven so, somehow, remnants of your former selves remain.\n$Until now you have yet to succeed, but I sense a different presence in you this time.\n\n$You are the only one here, though it is as if there is… another.\n$Will you finally prove a formidable challenge to me?\nThe challenge I have longed after for millennia?\n$We begin.", - "firstStageWin": "I see. The presence I felt was indeed real.\nIt appears I no longer need to hold back.\n$Do not disappoint me.", - "secondStageWin": "…Magnificent.", - "key_ordinal_one": "st", - "key_ordinal_two": "nd", - "key_ordinal_few": "rd", - "key_ordinal_other": "th" + "encounter": "又しても 時が満ちた 様 である。\nこちらへ 至る 理由は 存知するな。\n$汝は この場所へ 引かれた……\nこの何度となく 至った場所。\n$けれども 数えられぬとも 限らぬ……\n正確に宣ふ(のたまう)と 現在の 循環は {{cycleCount}}回目 である。\n$各循環に 心… 意識… 両方も 元の有様に 戻る。\nなれども 故吾の 残影は 汝の 中に 存する。\n$未だに 成功せぬ ままでも\n異なる 存在を 感ずる。\n$御座在る者は 一人。\nなれども 感ずるは… もう 他人。\n$到頭 汝から いかめしい 挑戦は 我が目にかかるか?\n千歳 万歳 相まってた挑戦……\n$始もう。", + "encounter_female": "又しても 時が満ちた 様 である。\nこちらへ 至る 理由は 存知するな。\n$汝は この場所へ 引かれた……\nこの何度となく 至った場所。\n$けれども 数えられぬとも 限らぬ……\n正確に宣ふ(のたまう)と 現在の 循環は {{cycleCount}}回目 である。\n$各循環に 心… 意識… 両方も 元の有様に 戻る。\nなれども 故吾の 残影は 汝の 中に 存する。\n$未だに 成功せぬ ままでも\n異なる 存在を 感ずる。\n$御座在る者は 一人。\nなれども 感ずるは… もう 他人。\n$到頭 汝から いかめしい 挑戦は 我が目にかかるか?\n千歳 万歳 相まってた挑戦……\n$始もう。", + "firstStageWin": "成る程。 感じた 存在は 正身(むざね) であった。\n自分を括る 必要 有らぬ 様である。\n$失望させぬが良い。", + "secondStageWin": "……お見事でございます。", + "key_ordinal_one": "", + "key_ordinal_two": "", + "key_ordinal_few": "", + "key_ordinal_other": "" } diff --git a/src/locales/ja/dialogue-misc.json b/src/locales/ja/dialogue-misc.json index 2f333b5f383..d0527564613 100644 --- a/src/locales/ja/dialogue-misc.json +++ b/src/locales/ja/dialogue-misc.json @@ -1,6 +1,6 @@ { - "ending": "@c{shock}You're back?@d{32} Does that mean…@d{96} you won?!\n@c{smile_ehalf}I should have known you had it in you.\n$@c{smile_eclosed}Of course… I always had that feeling.\n@c{smile}It's over now, right? You ended the loop.\n$@c{smile_ehalf}You fulfilled your dream too, didn't you?\nYou didn't lose even once.\n$I'll be the only one to remember what you did.\n@c{angry_mopen}I'll try not to forget!\n$@c{smile_wave_wink}Just kidding!@d{64} @c{smile}I'd never forget.@d{32}\nYour legend will live on in our hearts.\n$@c{smile_wave}Anyway,@d{64} it's getting late…@d{96} I think?\nIt's hard to tell in this place.\n$Let's go home. @c{smile_wave_wink}Maybe tomorrow, we can have another battle, for old time's sake?", - "ending_female": "@c{smile}Oh? You won?@d{96} @c{smile_eclosed}I guess I should've known.\nBut, you're back now.\n$@c{smile}It's over.@d{64} You ended the loop.\n$@c{serious_smile_fists}You fulfilled your dream too, didn't you?\nYou didn't lose even once.\n$@c{neutral}I'm the only one who'll remember what you did.@d{96}\nI guess that's okay, isn't it?\n$@c{serious_smile_fists}Your legend will always live on in our hearts.\n$@c{smile_eclosed}Anyway, I've had about enough of this place, haven't you? Let's head home.\n$@c{serious_smile_fists}Maybe when we get back, we can have another battle?\nIf you're up to it.", - "ending_endless": "Congratulations on reaching the current end!\nMore content is coming soon.", - "ending_name": "Devs" + "ending": "@c{shock}帰ってきた?@d{32} それなら…@d{96} 勝った っていうこと だろう?!\n@c{smile_ehalf}絶対 やれると思う べきだったな。\n$@c{smile_eclosed}もちろん、ずっと そんな気 がしたんだな。\n@c{smile}ついに 終わった だろう? ループを 断ち切った。\n$@c{smile_ehalf}キミの夢も 叶ったよな?\n一回も 負けなかった。\n$キミの しつくした事を 覚えるのは おれだけだ。\n@c{angry_mopen}忘れない ように するが…\n$@c{smile_wave_wink}なんつって!@d{64} @c{smile}一生 忘れない。@d{32}\nキミの伝説は いつまでも みんなの 心の中に 残っているから。\n$@c{smile_wave}とにかく、@d{64} そろそろ 遅くなる……@d{96} かな?\nこの場所で よく 分からない。\n$さあ、帰ろう。\n@c{smile_wave_wink}明日、昔のよしみで バトルでも しないか?", + "ending_female": "@c{smile}えぇ? 勝ちゃった?@d{96} @c{smile_eclosed}勝てるのが 分かる べきだったね。\nやっぱり 帰ってきた…\n$@c{smile}ついに終わった。@d{64} ループを 断ち切った。\n$@c{serious_smile_fists} アナタの夢も 叶ったよね?\n一回も 負けなかった!\n$@c{neutral}アタシだけが アナタが できた事 を覚えていく、ね。@d{96}\nでもね、たぶん 大丈夫なの かなぁ?\n$@c{serious_smile_fists}アナタの伝説は みんなの 心の中に\nずっと 残っているからね……\n$@c{smile_eclosed}じゃあ、こんなトコは もう飽きた だろう?\n帰ろうよ。\n$@c{serious_smile_fists}ふるさとに 着いたら、 また バトルしよう?\nやる気 あればね!", + "ending_endless": "現在のエンドまで やって来て おめでとう!\n更に多くの コンテンツは  近日公開予定です。", + "ending_name": "開発者" } diff --git a/src/locales/ja/dialogue.json b/src/locales/ja/dialogue.json index 6130ade1cb4..24cd19b6ffc 100644 --- a/src/locales/ja/dialogue.json +++ b/src/locales/ja/dialogue.json @@ -40,839 +40,839 @@ }, "lass": { "encounter": { - "1": "Let's have a battle, shall we?", - "2": "You look like a new trainer. Let's have a battle!", - "2_female": "You look like a new trainer. Let's have a battle!", - "3": "I don't recognize you. How about a battle?", - "4": "Let's have a fun Pokémon battle!", - "5": "I'll show you the ropes of how to really use Pokémon!", - "6": "A serious battle starts from a serious beginning! Are you sure you're ready?", - "6_female": "A serious battle starts from a serious beginning! Are you sure you're ready?", - "7": "You're only young once. And you only get one shot at a given battle. Soon, you'll be nothing but a memory.", - "8": "You'd better go easy on me, OK? Though I'll be seriously fighting!", - "9": "School is boring. I've got nothing to do. Yawn. I'm only battling to kill the time." + "1": "勝負しない?", + "2": "新人 トレーナーだろう? 勝負しよう!", + "2_female": "新しい トレーナーだろう? 勝負しよう!", + "3": "初めて 見る 顔ね! 勝負しない?", + "4": "ドッキドキで ワックワクな\nポケモン勝負に しよう!", + "5": "手取り 足取り 教えて あげる!\nホントの ポケモンの 使いかた!", + "6": "マジの 勝負 マジ始めっからー\n準備とか 覚悟とか オーケー?", + "6_female": "マジの 勝負 マジ始めっからー\n準備とか 覚悟とか オーケー?", + "7": "青春も バトルも 一度きり!!\nあなたを 思い出に させて!!", + "8": "あんた ちょっとは 手加減してよ…\nあたしは 本気で いくけど!", + "9": "学校 タルいし やることないし\nヒマだけあるから バトルしてんの。" }, "victory": { - "1": "That was impressive! I've got a lot to learn.", - "2": "I didn't think you'd beat me that bad…", - "2_female": "I didn't think you'd beat me that bad…", - "3": "I hope we get to have a rematch some day.", - "4": "That was pretty amazingly fun! You've totally exhausted me…", - "5": "You actually taught me a lesson! You're pretty amazing!", - "6": "Seriously, I lost. That is, like, seriously depressing, but you were seriously cool.", - "6_female": "Seriously, I lost. That is, like, seriously depressing, but you were seriously cool.", - "7": "I don't need memories like this. Deleting memory…", - "8": "Hey! I told you to go easy on me! Still, you're pretty cool when you're serious.", - "8_female": "Hey! I told you to go easy on me! Still, you're pretty cool when you're serious.", - "9": "I'm actually getting tired of battling… There's gotta be something new to do…" + "1": "すごい! 覚えること いっぱいね。", + "2": "新人に こんなに ぶっ壊されて なんて……", + "2_female": "新人に こんなに ぶっ壊されて なんて……", + "3": "またいつか 勝負を できると いいね!", + "4": "キミに ドッキ ドキドキ\n私は ボッロ ボロボロ……", + "5": "逆に 教えられちゃった!\nキミって 結構 スゴいんだ!", + "6": "マジ負けてる マジ凹む マジ辛い\nでも アンタは マジで イケてるかも。", + "6_female": "マジ負けてる マジ凹む マジ辛い\nでも アンタは マジで イケてるかも。", + "7": "こんな 思い出 いらないし\n記憶から 消しちゃおっと…", + "8": "ぷんぷん! 手加減してよね!!\n……本気の あんたも いいけどさ。", + "8_female": "ぷんぷん! 手加減してよね!!\n……本気の あんたも いいけどさ。", + "9": "バトルも そろそろ あきたわ 実際…\n何か 新しいこと とか ない?" } }, "breeder": { "encounter": { - "1": "Obedient Pokémon, selfish Pokémon… Pokémon have unique characteristics.", - "2": "Even though my upbringing and behavior are poor, I've raised my Pokémon well.", - "3": "Hmm, do you discipline your Pokémon? Pampering them too much is no good." + "1": "素直なヤツに ワガママなヤツ……\nポケモンにも 個性が あるんだよな。", + "2": "育ちも 素行も 悪い オレが\n育てた割に いい ポケモン だぜ!", + "3": "きみは ポケモンを しつけてるか?\n甘やかすだけじゃ ダメなんだぜ!" }, "victory": { - "1": "It is important to nurture and train each Pokémon's characteristics.", - "2": "Unlike my diabolical self, these are some good Pokémon.", - "3": "Too much praise can spoil both Pokémon and people." + "1": "ポケモンの 持っている 個性を\n活かして 育てることが 大切だ。", + "2": "極悪非道の オレと 違って\nこいつらは いいポケモン だよ!", + "3": "褒めてばっかりだと ポケモンも\n人も ダメに なっちゃうよな…" }, "defeat": { - "1": "You should not get angry at your Pokémon, even if you lose a battle.", - "2": "Right? Pretty good Pokémon, huh? I'm suited to raising things.", - "3": "No matter how much you love your Pokémon, you still have to discipline them when they misbehave." + "1": "負けたからって むやみに ポケモンを\n怒ったりしては ダメなんだぞ。", + "2": "な? なかなか いいポケモン だろ?\n育てるってのが 向いてるんだな オレ。", + "3": "可愛くて 大好きなポケモンでも\n悪いこと したら 叱らないとな!" } }, "breeder_female": { "encounter": { - "1": "Pokémon never betray you. They return all the love you give them.", - "2": "Shall I give you a tip for training good Pokémon?", - "3": "I have raised these very special Pokémon using a special method." + "1": "ポケモンは 裏切らないからね、\nささげた 愛情は 返ってくる。", + "2": "いいポケモンを 育てるコツを\nキミに 教えてあげようかな?", + "3": "秘密の 方法で 育てあげた\nとっても スペシャルな ポケモンだ。" }, "victory": { - "1": "Ugh… It wasn't supposed to be like this. Did I administer the wrong blend?", - "2": "How could that happen to my Pokémon… What are you feeding your Pokémon?", - "3": "If I lose, that tells you I was just killing time. It doesn't damage my ego at all." + "1": "うーん…… こんな はずでは……\nエキスの 配合を 間違えたか?", + "2": "ワイの 育てた ポケモンが そんな……\nキミは ポケモンに なに あげとるんや?", + "3": "負けたところで ただのヒマつぶし\n心に ダメージ ございません…" }, "defeat": { - "1": "This proves my Pokémon have accepted my love.", - "2": "The real trick behind training good Pokémon is catching good Pokémon.", - "3": "Pokémon will be strong or weak depending on how you raise them." + "1": "私の 愛情が ポケモンに\n届いてる 証拠 だわね。", + "2": "いいポケモンを 育てるコツは\nいいポケモンを 捕まえることだ。", + "3": "育てかた 次第で ポケモンは\n強くも 弱くも なるのだ。" } }, "fisherman": { "encounter": { - "1": "Aack! You made me lose a bite!\nWhat are you going to do about it?", - "2": "Go away! You're scaring the Pokémon!", - "3": "Let's see if you can reel in a victory!" + "1": "釣り糸が 絡まって イライラするーっ!\nええいっ 勝負だっ!", + "2": "海釣りと 川釣り キミは どっちが 好き?", + "3": "よーし! ポケモン好きと 釣り好きの 対決だ!" }, "victory": { - "1": "Just forget about it.", - "2": "Next time, I'll be reelin' in the triumph!", - "3": "Guess I underestimated the currents this time." + "1": "負けたっ! 余計に イライラするぞーっ!", + "2": "まるで 海釣りの ように 豪快に 負けたぞーっ!", + "3": "わあ! 糸が 絡まった! まさに 後の祭り…" } }, "fisherman_female": { "encounter": { - "1": "Woah! I've hooked a big one!", - "2": "Line's in, ready to reel in success!", - "3": "Ready to make waves!" + "1": "でっかいの 釣れたっ でっかいの!", + "2": "うちは 常に 大物ねらい!\n雑魚だったら 引っ込んで おくれよ!", + "3": "釣りたて ピチピチの\nポケモンで 勝負してあげよう!!" }, "victory": { - "1": "I'll be back with a stronger hook.", - "2": "I'll reel in victory next time.", - "3": "I'm just sharpening my hooks for the comeback!" + "1": "あれー 大きさで 負けたかな…", + "2": "なかなかの 引きな キミ!\nナイス ファイトだった!", + "3": "釣りあげられたのは……\nこの 私だったか!" } }, "swimmer": { "encounter": { - "1": "Time to dive in!", - "2": "Let's ride the waves of victory!", - "3": "Ready to make a splash!" + "1": "全力で 飛び込むよ!", + "2": "勝利の 大波に 乗っていくよ!", + "3": "完全に 浸しちゃうよ!" }, "victory": { - "1": "Drenched in defeat!", - "2": "A wave of defeat!", - "3": "Back to shore, I guess." + "1": "失敗で ぐしゃぐしゃ!", + "2": "敗北の 小波だった……", + "3": "そろそろ 漂って帰ろう……" } }, "backpacker": { "encounter": { - "1": "Pack up, game on!", - "2": "Let's see if you can keep pace!", - "3": "Gear up, challenger!", - "4": "I've spent 20 years trying to find myself… But where am I?" + "1": "旅する バックパッカー\n同じく 旅のトレーナーに 会う…", + "2": "私と 旅しているのは 人気の\nガイドブックで お薦めの ポケモン です!", + "3": "旅をしても していなくても\n私は 発見したいのです!", + "4": "旅を 続け 幾年月、\n知らない 国は ないくらい。" }, "victory": { - "1": "Tripped up this time!", - "2": "Oh, I think I'm lost.", - "3": "Dead end!", - "4": "Wait up a second! Hey! Don't you know who I am?" + "1": "きみと 出会うため\n旅していたのかも? なんて", + "2": "ガイドブックに 勝ち方は\n載っていないのよね…", + "3": "あっ! あなたの いいところ\n発見しちゃったかも!", + "4": "本当は ただ 「お帰り」 って\n言ってくれる ダーリンが 欲しい…" } }, "ace_trainer": { "encounter": { - "1": "You seem quite confident.", - "1_female": "You seem quite confident.", - "2": "Your Pokémon… Show them to me…", - "3": "Because I'm an Ace Trainer, people think I'm strong.", - "4": "Are you aware of what it takes to be an Ace Trainer?" + "1": "自信満々って 感じ ですな…", + "1_female": "自信満々って 感じ だね…", + "2": "君の ポケモン…… 私に 見せてごらんよ……", + "3": "エリートトレーナーなんて やってるから\n強い 人って 思われるんだ。", + "4": "エリートトレーナーたる 資格 って\nあなたは ご存じ かしら?" }, "victory": { - "1": "Yes… You have good Pokémon…", - "2": "What?! But I'm a battling genius!", - "3": "Of course, you are the main character!", - "3_female": "Of course, you are the main character!", - "4": "OK! OK! You could be an Ace Trainer!", - "4_female": "OK! OK! You could be an Ace Trainer!" + "1": "うん…… いいポケモンだね……", + "2": "勝負の 天才 いわれる\n私も 負けるこつの あっけんか!", + "3": "やはり あなたこそ 主人公だ!", + "3_female": "やはり あなたこそ 主人公だ!", + "4": "戦う 姿が なじんでるのね\nエリートトレーナーに なれるよ キミ!", + "4_female": "戦う 姿が なじんでるのね\nエリートトレーナーに なれるよ キミ!" }, "defeat": { - "1": "I am devoting my body and soul to Pokémon battles!", - "2": "All within my expectations… Nothing to be surprised about…", - "3": "I thought I'd grow up to be a frail person who looked like they would break if you squeezed them too hard.", - "4": "Of course I'm strong and don't lose. It's important that I win gracefully." + "1": "私はな ポケモン勝負に\n魂 かけてんだよォ!", + "2": "すべて 予想の はんちゅう……\n何の 驚きもないね……", + "3": "弱くて 抱きしめたら 折れそうな\n人に なるはずだったのにな。", + "4": "強いこと 負けないことは 当たり前……\nいかに 優雅に 勝つかが 重要 なの。" } }, "parasol_lady": { "encounter": { - "1": "Time to grace the battlefield with elegance and poise!" + "1": "傘が ないと パラソルおねえさんって\n言えないからさ 仕方なく 差してんの。" }, "victory": { - "1": "My elegance remains unbroken!" + "1": "傘がっ 傘が 折れちゃったっ!\nただの おねえさんに なっちゃう!" } }, "twins": { "encounter": { - "1": "Get ready, because when we team up, it's double the trouble!", - "2": "Two hearts, one strategy – let's see if you can keep up with our twin power!", - "3": "Hope you're ready for double trouble, because we're about to bring the heat!", - "3_female": "Hope you're ready for double trouble, because we're about to bring the heat!" + "1": "準備してね! あたしたちが 力を 組み合うと Wトラブルでちゅ!", + "2": "心は二つ、 作戦は一つ!\nツインパワーを 見せるなだ!", + "3": "二倍の 力で 熱いバトルを しましょうよ!", + "3_female": "二倍の 力で 熱いバトルを しましょうよ!" }, "victory": { - "1": "We may have lost this round, but our bond remains unbreakable!", - "2": "Our twin spirit won't be dimmed for long.", - "3": "We'll come back stronger as a dynamic duo!" + "1": "バトル 負けたけど、 絆 壊れないんでちゅ!", + "2": "何が起こっても ツイン魂が あせてはしないよ!", + "3": "二倍、 いや、 四倍の 力で 戻って来るよ!" }, "defeat": { - "1": "Twin power reigns supreme!", - "2": "Two hearts, one triumph!", - "3": "Double the smiles, double the victory dance!" + "1": "ツインパワーに 誰も 勝てない!", + "2": "心は二つ、 勝利は一つ!", + "3": "笑顔も 勝利の舞も 二倍!" } }, "cyclist": { "encounter": { - "1": "Get ready to eat my dust!", - "2": "Gear up, challenger! I'm about to leave you in the dust!", - "3": "Pedal to the metal, let's see if you can keep pace!" + "1": "両足 砕けようとも 漕ぐ、\nこれ サイクリング 極意なり…", + "2": "やー 悪いねー ついつい パートナー\n連れてきちゃったわ ラブラブだからさ!", + "3": "もうダメェ! 物足りないのォ!\n自転車じゃ 物足りないよォ!" }, "victory": { - "1": "Spokes may be still, but determination pedals on.", - "2": "Outpaced!", - "3": "The road to victory has many twists and turns yet to explore." + "1": "我が サイクリングは 無限\nいずれまた 貴様に 挑む…", + "2": "冷たいボディに ふんわりサドル\nコイツは 最高の パートナーだぜ…", + "3": "自転車じゃ 満足できないのォ!\n暴走族に 入れてもらうのォ!" } }, "black_belt": { "encounter": { - "1": "I praise your courage in challenging me! For I am the one with the strongest kick!", - "2": "Oh, I see. Would you like to be cut to pieces? Or do you prefer the role of punching bag?", - "2_female": "Oh, I see. Would you like to be cut to pieces? Or do you prefer the role of punching bag?" + "1": "宇宙一の キックを 持つ\nわしに 挑むとは 褒めてやろう!", + "2": "んー そうだね、 ズタズタ されたい?\nそれとも ボロボロが 好み かな?", + "2_female": "んー そうだね、 ズタズタ されたい?\nそれとも ボロボロが 好み かな?" }, "victory": { - "1": "Oh. The Pokémon did the fighting. My strong kick didn't help a bit.", - "2": "Hmmm… If I was going to lose anyway, I was hoping to get totally messed up in the process." + "1": "あ 戦うのは ポケモンだっけ\nわしの キックは 関係ないわ。", + "2": "んー…… どうせ 負けるなら\nボクは メチャクチャに されたかったんだけどね。" } }, "battle_girl": { "encounter": { - "1": "You don't have to try to impress me. You can lose against me." + "1": "いいのよ、 見栄 張らなくても\n私には 負けてもいいのよ。" }, "victory": { - "1": "It's hard to say good-bye, but we are running out of time…" + "1": "さようならするのは つらいけれど\nもう 時間が ないわね……" } }, "hiker": { "encounter": { - "1": "My middle-age spread has given me as much gravitas as the mountains I hike!", - "2": "I inherited this big-boned body from my parents… I'm like a living mountain range…" + "1": "中年太りの このボディ\n山の ごとき 貫禄で ゴワス!", + "2": "親 から もらった メタボな ボディ……\n生ける 山脈とは オレの こと……" }, "victory": { - "1": "At least I cannot lose when it comes to BMI!", - "2": "It's not enough… It's never enough. My bad cholesterol isn't high enough…" + "1": "皮下脂肪 だったら\n負けないのにで ゴワス!", + "2": "足りぬ…… 足りぬぞ……\n悪玉コレステロールが 足りぬぞ……" } }, "ranger": { "encounter": { - "1": "When I am surrounded by nature, most other things cease to matter.", - "2": "When I'm living without nature in my life, sometimes I'll suddenly feel an anxiety attack coming on." + "1": "自然に 囲まれ 暮らしているとな\n大抵の ことが どうでも良くなる……", + "2": "自然から 離れ 暮らしているとな\n時々 急に 苦しくなるのさ。" }, "victory": { - "1": "It doesn't matter to the vastness of nature whether I win or lose…", - "2": "Something like this is pretty trivial compared to the stifling feelings of city life." + "1": "ぼくが 負けたって 大自然に\nとっては どうだって いいことさ……", + "2": "都会の 息苦しさに 比べれば\nこんなこと へ でも ないさ……" }, "defeat": { - "1": "I won the battle. But victory is nothing compared to the vastness of nature…", - "2": "I'm sure how you feel is not so bad if you compare it to my anxiety attacks…" + "1": "ぼくが 勝ったことも 大自然に\n比べたら どうだっていいのさ……", + "2": "僕の 不安に 比べれば\n大したこと ないさ きっと……" } }, "scientist": { "encounter": { - "1": "My research will lead this world to peace and joy." + "1": "ボクノ 研究ガ 世界ヲ\n平和ト 幸セニ 導クノデス。" }, "victory": { - "1": "I am a genius… I am not supposed to lose against someone like you…" + "1": "ボクハ 天才 ナンダ……\nコンナ 相手ニ 負ケルハズ……" } }, "school_kid": { "encounter": { - "1": "…Heehee. I'm confident in my calculations and analysis.", - "2": "I'm gaining as much experience as I can because I want to be a Gym Leader someday." + "1": "……グフフ 計算と 分析には\n自信が ありますからね 僕!", + "2": "ここで いっぱい 経験 積んで\nいつかは ジムリーダーを 目指すんだ!" }, "victory": { - "1": "Ohhhh… Calculation and analysis are perhaps no match for chance…", - "2": "Even difficult, trying experiences have their purpose, I suppose." + "1": "ムググ…… 計算も 分析も\n偶然には 敵わないか……", + "2": "辛く 悲しい 経験も\nいつかは 役に 立つはずです……" } }, "artist": { "encounter": { - "1": "I used to be popular, but now I am all washed up." + "1": "かつては おれも 売れっ子だったが\n今では だれにも 相手にされん……" }, "victory": { - "1": "As times change, values also change. I realized that too late." + "1": "時代が 変われば 価値も 移ろう\nそれに 気がつくのが 遅かったのだ……" } }, "guitarist": { "encounter": { - "1": "Get ready to feel the rhythm of defeat as I strum my way to victory!" + "1": "ロックン ロールッ!\n戦いは リズムッ!" }, "victory": { - "1": "Silenced for now, but my melody of resilience will play on." + "1": "ぎゅぎゅーん……\n悲しい メロディ だぜ……" } }, "worker": { "encounter": { - "1": "It bothers me that people always misunderstand me. I'm a lot more pure than everyone thinks." + "1": "作業員 ってのは 命がけ\nだけど やりがいは 十分よ!" }, "victory": { - "1": "I really don't want my skin to burn, so I want to stay in the shade while I work." + "1": "100年 残る ビルや 橋を\n建てるなんて ロマン だろう?" } }, "worker_female": { "encounter": { - "1": "It bothers me that people always misunderstand me.\n$I'm a lot more pure than everyone thinks." + "1": "よく 勘違い されて 困るんだけど\nみんなが 思うよりも ナイーブ なのね。" }, "victory": { - "1": "I really don't want my skin to burn, so I want to stay in the shade while I work." + "1": "本当は お肌 焼きたくない から\n日陰 でしか 作業 したくないのね……" }, "defeat": { - "1": "My body and mind aren't necessarily always in sync." + "1": "心と体は 必ずしも\n一致するわけでは ないのね……" } }, "worker_double": { "encounter": { - "1": "I'll show you we can break you. We've been training in the field!" + "1": "現場で 鍛えた ワシらの 力\n今 見せちゃるけえのぉ!" }, "victory": { - "1": "How strange… How could this be… I shouldn't have been outmuscled." + "1": "おかしいのぉ なんでかのぉ\n力で 負けるはず ないんじゃが……" } }, "hex_maniac": { "encounter": { - "1": "I normally only ever listen to classical music, but if I lose, I think I shall try a bit of new age!", - "2": "I grow stronger with each tear I cry." + "1": "普段 クラシックしか 聴きませんが 負けたら ラップを してみます!", + "2": "涙の 数だけ 強くなれるの だって\nわたし 女の子 だもん。" }, "victory": { - "1": "Is this the dawning of the age of Aquarius?", - "2": "Now I can get even stronger. I grow with every grudge." + "1": "アタイの 想いは アイツに 重い!\n呪いと 願いは 大体 同じッ!", + "2": "これで わたしは また 強くなれる\n恨んだ数だけ 成長 するの。" }, "defeat": { - "1": "New age simply refers to twentieth century classical composers, right?", - "2": "Don't get hung up on sadness or frustration. You can use your grudges to motivate yourself.", - "2_female": "Don't get hung up on sadness or frustration. You can use your grudges to motivate yourself." + "1": "ラッパーって ラップ現象を\n起こすのが 仕事なんですよね?", + "2": "辛いことや 悲しいことを バネにせず\nそのまま 恨みの 力に 変えるんだ。", + "2_female": "辛いことや 悲しいことを バネにせず\nそのまま 恨みの 力に 変えるんだ。" } }, "psychic": { "encounter": { - "1": "Hi! Focus!" + "1": "ハイッ!! 集中ッ!!" }, "victory": { - "1": "Eeeeek!" + "1": "フギャンッ!!" } }, "officer": { "encounter": { - "1": "Brace yourself, because justice is about to be served!", - "2": "Ready to uphold the law and serve justice on the battlefield!" + "1": "ボクは お巡り なんだよ!!\nつまり ボクは ジャスティスだよ!", + "2": "さぼってないのでー ありますっ!\nパトロールなのでー ありますっ!" }, "victory": { - "1": "The weight of justice feels heavier than ever…", - "2": "The shadows of defeat linger in the precinct." + "1": "ボクはっ ボクは お巡りだぞぉ!!\nジャスティス なのにーっ!!", + "2": "キミ 要注意でー ありますっ!\nマークしちゃうでー ありますっ!" } }, "beauty": { "encounter": { - "1": "My last ever battle… That's the way I'd like us to view this match…" + "1": "これが 最後の 勝負……\nそう 思って 相手するわね。" }, "victory": { - "1": "It's been fun… Let's have another last battle again someday…" + "1": "楽しかった……\nまた 最後の 勝負を したいものね。" } }, "baker": { "encounter": { - "1": "Hope you're ready to taste defeat!", - "1_female": "Hope you're ready to taste defeat!" + "1": "きみは もうすぐ\n敗北を 味わっちゃうわ!", + "1_female": "君は もうすぐ\n敗北を 味わっちゃうわ!" }, "victory": { - "1": "I'll bake a comeback." + "1": "諦める べーき かもね……" } }, "biker": { "encounter": { - "1": "Time to rev up and leave you in the dust!" + "1": "おんしゃー 覚悟は できとるんか?\n暴走族 なめたら いかんぜよ!" }, "victory": { - "1": "I'll tune up for the next race." + "1": "ボクらぁー ほんまは 真面目です!\nちっくとも 悪いことは せんです!!" } }, "firebreather": { "encounter": { - "1": "My flames shall devour you!", - "2": "My soul is on fire. I'll show you how hot it burns!", - "3": "Step right up and take a look!" + "1": "やけたとうで 火を 吹く 練習をした!\n良い子は 真似 すんなよ!", + "2": "俺たち 火吹きやろうは 誰よりも 火の こわさを 知ってるのさ!", + "3": "よってらっしゃい みてらっしゃい!" }, "victory": { - "1": "I burned down to ashes...", - "2": "Yow! That's hot!", - "3": "Ow! I scorched the tip of my nose!" + "1": "おっと 誤解 しないでくれ!\nあのとうが 燃えたのは 俺のせいじゃ ないんだ!", + "2": "アツい 勝負を ありがとう!", + "3": "あちちち 鼻の 先っぽ 焦げちゃった!" } }, "sailor": { "encounter": { - "1": "Matey, you're walking the plank if you lose!", - "2": "Come on then! My sailor's pride is at stake!", - "3": "Ahoy there! Are you seasick?", - "3_female": "Ahoy there! Are you seasick?" + "1": "うっしゃー! 負けたら 海に 落とすぞー!", + "2": "そら 来い! 船乗り 魂に かけて 勝つ!", + "3": "おい あんた! 乗ってて 船酔い しないか?", + "3_female": "おい あんた! 乗ってて 船酔い しないか?" }, "victory": { - "1": "Argh! Beaten by a kid!", - "2": "Your spirit sank me!", - "3": "I think it's me that's seasick..." + "1": "くー やられた!", + "2": "船乗り 魂も お前には 負けた!", + "3": "船酔いしてるのが 俺か……" } }, "archer": { "encounter": { - "1": "Before you go any further, let's see how you fare against us, Team Rocket!", - "2": "I have received reports that your skills are not insignificant. Let's see if they are true.", - "3": "I am Archer, an Admin of Team Rocket. And I do not go easy on enemies of our organization." + "1": "先に進む 前に 我々 ロケット団と 戦って もらおうか?", + "2": "あなたも 相当の腕だと 報告が 来ています。 確認しましょう。", + "3": "私は ロケット団幹部の アポロ。\n私達の 敵に 手加減 しませんよ!" }, "victory": { - "1": "What a blunder!", - "2": "With my current skills, I was not up to the task after all.", - "3": "F-forgive me, Giovanni... For me to be defeated by a mere trainer..." + "1": "……なんという 失態!", + "2": "今の腕で やはり 私では 無理 でしたか……", + "3": "さ サカキ様 お許しください\n私とも あろう ものが……" } }, "ariana": { "encounter": { - "1": "Hold it right there! We can't someone on the loose.\n$It's harmful to Team Rocket's pride, you see.", - "2": "I don't know or care if what I'm doing is right or wrong...\n$I just put my faith in Giovanni and do as I am told", - "3": "Your trip ends here. I'm going to take you down!" + "1": "そこまでよーーーっ!!\n$あなたみたいな ヤツを いつまでも のさばらせて おいたら\nロケット団の プライドは キズついて キズついて キズだらけに なっちゃうのよー!", + "2": "私たちの している ことが\n正しいかどうか なんて どうでもいい…\n$私は サカキ様を 信じて ただ 付いてきた のよ!", + "3": "ここは 通さないよ。\nだって 私が 勝つんだから!" }, "victory": { - "1": "Tch, you really are strong. It's too bad.\n$If you were to join Team Rocket, you could become an Executive.", - "1_female": "Tch, you really are strong. It's too bad.\n$If you were to join Team Rocket, you could become an Executive.", - "2": "I... I'm shattered...", - "3": "Aaaieeeee! This can't be happening! I fought hard, but I still lost…" + "1": "あら 強いのね 残念ね\nあなたなら ロケット団に くれば 幹部に だって なれるかもよ。", + "1_female": "あら 強いのね 残念ね\nあなたなら ロケット団に くれば 幹部に だって なれるかもよ。", + "2": "ま まけたわ……", + "3": "くききききーっ! 全力で 戦ったのに……\nこれでも 勝てない なんて!" } }, "proton": { "encounter": { - "1": "What do you want? If you interrupt our work, don't expect any mercy!", - "2": "What do we have here? I am often labeled as the scariest and cruelest guy in Team Rocket…\n$I strongly urge you not to interfere with our business!", - "3": "I am Proton, an Admin of Team Rocket. I am here to put an end to your meddling!" + "1": "まちな! ロケット団の 砦と言われた この私!\nこれ以上 先には 行かせません!", + "2": "なんですか? 私は ロケット団で もっとも 冷酷と 呼ばれた 男……\n$私達の 仕事の ジャマなど させはしませんよ!", + "3": "私は ロケット団幹部の ランス。\nこれ以上 仕事の ジャマは させませんよ!" }, "victory": { - "1": "The fortress came down!", - "2": "You may have won this time… But all you did was make Team Rocket's wrath grow…", - "3": "I am defeated… But I will not forget this!" + "1": "砦が 崩れました……", + "2": "私に 勝った ところで\n所詮は ロケット団の 怒りを 強めた だけですよ……", + "3": "くっ! なかなか やりますね。\nしかし、 次からは そうは 行きません!" } }, "petrel": { "encounter": { - "1": "Muhahaha, we've been waiting for you. Me? You don't know who I am? It is me, Giovanni.\n$The majestic Giovanni himself! Wahahaha! …Huh? I don't sound anything like Giovanni?\n$I don't even look like Giovanni? How come? I've worked so hard to mimic him!", - "2": "I am Petrel, an Admin of Team Rocket. I will not allow you to interfere with our plans!", - "3": "Rocket Executive Petrel will deal with this intruder!" + "1": "ぐっふっふっ よくきたな…\nおや? 私が 誰か 分からんかね?\n$サカキだよ サカキ様 だよ!\nぐわぁーっはっはーっ!\n$……あれ? 全然 似てない? サカキ様に 見えない?\nくっそー 一生懸命 練習したのに!", + "2": "私は ロケット団幹部の ラムダ。\n計画を 邪魔するのは 許さない!", + "3": "侵入者には  ロケット団幹部の ラムダが 対処するぞ!" }, "victory": { - "1": "OK, OK. I'll tell you where he is.", - "2": "I… I couldn't do a thing… Giovanni, please forgive me…", - "3": "No, I can't let this affect me. I have to inform the others…" + "1": "わ 分かった…… 局長の 居場所 教える……", + "2": "ぐうう…… まったく 歯が立たない……\nサカキ様 お許し ください……", + "3": "いかん 負けて 落ち込んでる 場合じゃない\n仲間に 知らせなくては……" } }, "tabitha": { "encounter": { - "1": "Hehehe! So you've come all the way here! But you're too late!", - "2": "Hehehe... Got here already, did you? We underestimated you! But this is it! \n$I'm a cut above the Grunts you've seen so far. I'm not stalling for time.\n$I'm going to pulverize you!", - "3": "I'm going to give you a little taste of pain! Resign yourself to it!" + "1": "ウヒョヒョ! お前 ここまで 来たのか! だけど 遅かったぜ!", + "2": "ウヒョヒョ…… もう ここまで 来たのか!\n思っていたより やるな! だが ここまでだ!\n$俺は これまでの 下っ端ども とは 一味違う!\n時間稼ぎなんか しねえで コテンパンしてやるぜ!", + "3": "ウヒョヒョ! 痛みを 味わわせて あげますよ! 身を 委ねろ!" }, "victory": { - "1": "Hehehe! You might have beaten me, but you don't stand a chance against the boss!\n$If you get lost now, you won't have to face a sound whipping!", - "2": "Hehehe... So, I lost, too...", - "3": "Ahya! How could this be? For an Admin like me to lose to some random trainer...", - "3_female": "Ahya! How could this be? For an Admin like me to lose to some random trainer..." + "1": "「ウヒョヒョ‥‥! 俺に 勝てても リーダーには 勝てないぜ!\n$さっさと 帰ったほうが 痛い 思い しなくて 済むぜ!", + "2": "ウヒョヒョ…… 負けちまったか……", + "3": "うっひょーん……! なんたる事だろうか……!\nサブリーダーの ホムラさんが こんな デタラメなヤツ なぞに……", + "3_female": "うっひょーん……! なんたる事だろうか……!\nサブリーダーの ホムラさんが こんな デタラメなヤツ なぞに……" } }, "courtney": { "encounter": { - "1": "Don't. Get. In. My. Way.", - "2": "You... ...I want to...analyze. Ahahaha", - "3": "... Well then...Deleting..." + "1": "………ジャっ…マ……ッ\nするなあああああッッッッ!!!!!!", + "2": "キミを …………アナライズ\n…………したい …………ァハハハッ♪", + "3": "…………………じゃあ\n…………………デリートします" }, "victory": { - "1": "Hah hah... Uhn...hah hah...", - "2": "As anticipated. Unanticipated. You. Target lock...completed.\n$Commencing...experiment. You. Forever. Aha... ♪", - "3": "That's unanticipated. ...I knew it. You...are interesting! ...Haha. ♪" + "1": "…………はぁはぁ……\n………んぅ…はぁはぁ………", + "2": "…………予想内 …………予想外\n…………キミ …………ターゲットロック …………したから\n$…………エクスペリメント …………するから\n…………キミを …………ずっと …………ァハッ……♪", + "3": "…………また …………予想外\n…………やっぱり …………キミ……オモチロイ……! ……ァハハッ……♪" } }, "shelly": { "encounter": { - "1": "Ahahahaha! You're going to meddle in Team Aqua's affairs?\n$You're either absolutely fearless, simply ignorant, or both!\n$You're so cute, you're disgusting! I'll put you down", - "2": "What's this? Who's this spoiled brat?", - "3": "Cool your jets. Be patient. I'll crush you shortly.", - "3_female": "Cool your jets. Be patient. I'll crush you shortly." + "1": "オーッホッホ! 我々 アクア団の\n邪魔を しようと 言うの!?\n$もう 怖いもの知らず と言おうか\nただの 愚か者 と言おうか……\n$かわいすぎて 憎らしく なっちゃう!\nやっつけて あげるわね!", + "2": "ああん? なんだい?\nこの クソ生意気な オコチャマは……?", + "3": "熱くなってん じゃないよ、 少しは 頭を 冷やしな。\nすぐ ぶっ壊すから。", + "3_female": "熱くなってん じゃないよ、 少しは 頭を 冷やしな。\nすぐ ぶっ壊すから。" }, "victory": { - "1": "Ahahahaha! We got meddled with unexpectedly! We're out of options.\n$We'll have to pull out. But this isn't the last you'll see of Team Aqua!\n$We have other plans! Don't you forget it!", - "2": "Ahhh?! Did I go too easy on you?!", - "3": "Uh. Are you telling me you've upped your game even more during the fight?\n$You're a brat with a bright future… My Pokémon and I don't have any strength left to fight…\n$Go on… Go and be destroyed by Archie.", - "3_female": "Uh. Are you telling me you've upped your game even more during the fight?\n$You're a brat with a bright future… My Pokémon and I don't have any strength left to fight…\n$Go on… Go and be destroyed by Archie." + "1": "オーッホッホ! 思わぬ 邪魔が 入っちゃったわ!\n仕方ないわね!\n$ここは 一度 引き上げちゃう! でもね アクア団の\n活動は まだまだ 続くんだから 覚えておきなさーい!", + "2": "くううっ……!?\n手を 抜きすぎちゃった かしら……!", + "3": "……うぅ ……この間 よりも\n更に ウデを上げてる ですって……!?\n$末恐ろしい オコチャマだわ……\n……アタシと ポケモンたちに もう 戦うチカラは 残っちゃいない\n$……行きなさいよ 行って\nアオギリ様に 粛清されるが いいわ。", + "3_female": "……うぅ ……この間 よりも\n更に ウデを上げてる ですって……!?\n$末恐ろしい オコチャマだわ……\n……アタシと ポケモンたちに もう 戦うチカラは 残っちゃいない\n$……行きなさいよ 行って\nアオギリ様に 粛清されるが いいわ。" } }, "matt": { "encounter": { - "1": "All right then, until the boss has time for you, I'll be your opponent!", - "2": "Hooah! Full on! I'm burning up! Well! Welll! Wellllll! Let's battle it out until we've got nothing left!", - "3": "Hoo hah! I'm gonna smash you up!" + "1": "……ってな ワケでヨォ あにィが ジカンが開くマデ\nオレッちの 相手を してもらうゼぃ!", + "2": "フウハアッ!! マックスッ!!\nタギってッ!! きたゼェェェェッ!!!!!\n$サアッ! サアッ! サアアアッ!!!\nチカラ 尽きハテるまで ヤリあおうゼッッ!!!", + "3": "UPAAAAA!!!\nモミツブシテ ヤルゼェェェ!!" }, "victory": { - "1": "Muwuhahaha! That battle was fun even though I lost!", - "2": "I can feel it! I can feel it, all right! The strength coming offa you!\n$More! I still want more! But looks like we're outta time...", - "3": "Oho! That's a loss I can be proud of!", - "3_female": "Oho! That's a loss I can be proud of!" + "1": "フゥーハッハッハァァァ!!!\nマケても タノしい ショウブ だったゼ!", + "2": "ビンビン かんじるゼェ!! オメェの ツヨサ!\n$モミつぶせる ときを タノシミに してるゼィ!", + "3": "オウホウッ! タカぶる ハイボク だぜっ!", + "3_female": "オウホウッ! タカぶる ハイボク だぜっ!" } }, "mars": { "encounter": { - "1": "I'm Mars, one of Team Galactic's top Commanders.", - "2": "Team Galactic's vision for the future is unwavering. Opposition will be crushed without mercy!", - "3": "Feeling nervous? You should be!", - "3_female": "Feeling nervous? You should be!" + "1": "あたしは ギンガ団幹部の マーズ!\n強くて 美しいの!", + "2": "ギンガ団は 生み出そうと しているのは 新しい 世界。\nジャマは 許さないわ!", + "3": "ボスの ジャマは させないわよ!\nこの先に 進みたいなら あたしが 相手するわ!", + "3_female": "ボスの ジャマは させないわよ!\nこの先に 進みたいなら あたしが 相手するわ!" }, "victory": { - "1": "This can't be happening! How did I lose?!", - "2": "You have some skill, I'll give you that.", - "3": "Defeated... This was a costly mistake." + "1": "負けた……! ギンガ団の 幹部 として……\nこんなことって ありえない!!", + "2": "何なのよッ! あたしのこと 嫌いなの!?", + "3": "まさか! 負けるだなんて!?\n生意気な ヤツね!!" } }, "jupiter": { "encounter": { - "1": "Jupiter, Commander of Team Galactic, at your service.", - "2": "Resistance is futile. Team Galactic will prevail!", - "3": "You're trembling... scared already?" + "1": "いいわ!\nこの ジュピターが 相手 してあげましょう!", + "2": "反対なんて 虚しい!\nギンガ団は 勝利するわ!", + "3": "どうしたの? もしかして 震えているのかしら?" }, "victory": { - "1": "No way... I lost?!", - "2": "Impressive, you've got guts!", - "3": "Losing like this... How embarrassing." + "1": "フン! 相変わらずの 強さ ちっとも かわいくないわね!", + "2": "フン! なかなか やるじゃない!", + "3": "フン! 次は 泣かして あげるんだから!" } }, "saturn": { "encounter": { - "1": "I am Saturn, Commander of Team Galactic.", - "2": "Our mission is absolute. Any hindrance will be obliterated!", - "3": "Is that fear I see in your eyes?" + "1": "ミッションは 順調!\nボスも 満足 なさるだろう。\n$全ては みんなの ために\nそして ギンガ団の ために!", + "2": "ギンガ団の 使命を ジャマするなら\nどんな 可能性でも 潰す!", + "3": "わたしたち ギンガ団は 必要な ものを 独占し\n要らない ものは 捨てるだけ!" }, "victory": { - "1": "Impossible... Defeated by you?!", - "2": "You have proven yourself a worthy adversary.", - "3": "Bestowed in defeat... This is unacceptable." + "1": "強い! だが 哀れだな。", + "2": "……なるほど 強い!\nギンガ団に 歯向かう わけだ。", + "3": "くっ! この わたしが!\n時間稼ぎにしか ならないだと…\n$まあ いい! おまえが 何をしても\n流れる 時間は 止められない!" } }, "zinzolin": { "encounter": { - "1": "You could become a threat to Team Plasma, so we will eliminate you here and now!", - "1_female": "You could become a threat to Team Plasma, so we will eliminate you here and now!", - "2": "You don't have the sense to know when to quit, it seems. It's an act of mercy on my part to bring an end to this now!", - "3": "You're an impressive Trainer to have made it this far. But it ends here.", - "3_female": "You're an impressive Trainer to have made it this far. But it ends here." + "1": "さて…… おまえは プラズマ団に とって\n不安要素に なりかねない。\n$ここで 排除するのだ!", + "1_female": "さて…… おまえは プラズマ団に とって\n不安要素に なりかねない。\n$ここで 排除するのだ!", + "2": "諦めきれぬか? なら 引導を 渡すのが\nワタシなりの 優しさ なのだ!", + "3": "ここまで 来るとは 大した トレーナー だが、\n今は 終わりだ。", + "3_female": "ここまで 来るとは 大した トレーナー が、\n今は 終わりだ。" }, "victory": { - "1": "Ghetsis... I have failed you...", - "2": "It's bitter cold. I'm shivering. I'm suffering. Yet, we will stand victorious.", - "3": "Hmph. You're a smarter Trainer than I expected, but not smart enough.", - "3_female": "Hmph. You're a smarter Trainer than I expected, but not smart enough." + "1": "ゲーチス様…… 失望させました……", + "2": "寒い。 ワタシは 震えている。\n苦しいが いつか ワタシたちは 成功する。", + "3": "ふむう。 存外 さとい トレーナーだ。", + "3_female": "ふむう。 存外 さとい トレーナーだ。" } }, "rood": { "encounter": { - "1": "You are a threat to Team Plasma. We cannot let you walk away from here and now!", - "1_female": "You are a threat to Team Plasma. We cannot let you walk away from here and now!", - "2": "It seems you don't know when to give up. I'll make sure no one interferes with our plans!", - "3": "You are a remarkable Trainer to have made it this far. But this is where it ends.", - "3_female": "You are a remarkable Trainer to have made it this far. But this is where it ends." + "1": "おまえ という トレーナーが\nどんな 人物か 見せてもらいたい…\n$そう、 ポケモン勝負 でな。", + "1_female": "おまえ という トレーナーが\nどんな 人物か 見せてもらいたい…\n$そう、 ポケモン勝負 でな。", + "2": "見限らぬようだ。 だれにも ジャマは させぬよ。", + "3": "……これが おまえの\n望みと あらば", + "3_female": "……これが おまえの\n望みと あらば" }, "victory": { - "1": "Ghetsis... I have failed my mission...", - "2": "The cold is piercing. I'm shivering. I'm suffering. Yet, we will stand triumphant.", - "3": "Hm. You are a talented Trainer, but unfortunately not talented enough." + "1": "ほう! ポケモンと 心が\n通じているような 戦い方……", + "2": "失敗しました……\nそぞろに 潮の 匂いが 恋しくなる……", + "3": "正直言って…… ゲーチス様の なにが 真実で\nなにが 虚構か わからないがね……" } }, - "xerosic": { +"xerosic": { "encounter": { - "1": "Ah ha ha! It would be my pleasure. Come on, little Trainer! Let's see what you've got!", - "1_female": "Ah ha ha! It would be my pleasure. Come on, little Trainer! Let's see what you've got!", - "2": "Hmm... You're more powerful than you look. I wonder how much energy there is inside you.", - "2_female": "Hmm... You're more powerful than you look. I wonder how much energy there is inside you.", - "3": "I've been waiting for you! I need to do a little research on you! Come, let us begin!" + "1": "オマエを 倒せば ワタシの 科学力の\nすごさを 証明できるゾ! よし いくのだ!", + "1_female": "オマエを 倒せば ワタシの 科学力の\nすごさを 証明できるゾ! よし いくのだ!", + "2": "なるほど ジャマを したのは オマエだったのか!\nわかったゾ! よーし! オマエで 実験だゾ!", + "2_female": "なるほど ジャマを したのは オマエだったのか!\nわかったゾ! よーし! オマエで 実験だゾ!", + "3": "おおー ウワサの オマエか 待っていたゾ!\nオマエを 調べる ほら 始めるゾ!" }, "victory": { - "1": "Ah, you're quite strong. Oh yes—very strong, indeed.", - "2": "Ding-ding-ding! You did it! To the victor go the spoils!", - "2_female": "Ding-ding-ding! You did it! To the victor go the spoils!", - "3": "Wonderful! Amazing! You have tremendous skill and bravery!" + "1": "グヌウウ…… なぜだ……\nなぜ こんなことが 起こるのだ……?", + "2": "なんだと! オマエ すごいゾ!\nオマエの ポケモン すごいゾ!", + "2_female": "なんだと! オマエ すごいゾ!\nオマエの ポケモン すごいゾ!", + "3": "すごいな オマエ! すごいぞ オマエ!\nワタシは オマエを 認める イコール 金を あげる!" } }, "bryony": { "encounter": { - "1": "I am Bryony, and it would be my pleasure to battle you. Show me what you've got.", - "2": "Impressive... You're more powerful than you appear. Let's see the true extent of your energy.", - "2_female": "Impressive... You're more powerful than you appear. Let's see the true extent of your energy.", - "3": "I've anticipated your arrival. It's time for a little test. Shall we begin?" + "1": "一人で やっつける。\n勝てる 確率を あげる 必要ない。", + "2": "どこかで 見た 顔?\nわかんないけど フレア団 じゃないし やっつけようよ…", + "2_female": "どこかで 見た 顔?\nわかんないけど フレア団 じゃないし やっつけようよ…", + "3": "あら? あら?" }, "victory": { - "1": "You're quite strong. Oh yes—very strong, indeed.", - "2": "Ding-ding-ding! You've done well. Victory is yours.", - "3": "Wonderful! Remarkable! Your skill and bravery are commendable." + "1": "確率は あくまでも 確率。\n絶対では ないのよね……", + "2": "確率を 無視する トレーナー\nあなたの パワーの 源は?", + "3": "あらま! こいつめ!\nわたしが かわいそうでしょ!" } }, "rocket_grunt": { "encounter": { - "1": "Prepare for trouble!", - "2": "We're pulling a big job here! Get lost, kid!", - "2_female": "We're pulling a big job here! Get lost, kid!", - "3": "Hand over your Pokémon, or face the wrath of Team Rocket!", - "4": "You're about to experience the true terror of Team Rocket!", - "5": "Hey, kid! Me am a Team Rocket member kind of guy!", - "5_female": "Hey, kid! Me am a Team Rocket member kind of guy!" + "1": "なんだかんだと 聞かれたら\n答えてあげるのが 世の情け!", + "2": "おれ達は 大事な 仕事を してるんだ! お家へ 帰りな!", + "2_female": "おれ達は 大事な 仕事を してるんだ! お家へ 帰りな!", + "3": "ロケット団の 恐ろしさを 知りたくないなら\nおまえの ポケモンを よこせ!", + "4": "ロケット団の 本当の 恐ろしさ\nおまえに 教えて 差しあげよう!", + "5": "ロケット団に ガイコクジン\nワタシ オンリーワン だけど……\n$しかーし! そんなコト ノー カンケー!", + "5_female": "ロケット団に ガイコクジン\nワタシ オンリーワン だけど……\n$しかーし! そんなコト ノー カンケー!" }, "victory": { - "1": "Team Rocket blasting off again!", - "2": "Oh no! I dropped the Lift Key!", - "3": "I blew it!", - "4": "My associates won't stand for this!", - "5": "You say what? Team Rocket bye-bye a go-go? Broken it is says you?" + "1": "やな感じ~!", + "2": "しまった…! せっかく 隠して置いた エレベータの カギが…!", + "3": "しくじったか!", + "4": "くそ! 仲間が 黙っちゃ いねえぞ!", + "5": "オー ノー! キャント ビリーブ!\nユーは ブルーベリー ストロベリー!\n$……ソーリー ミステイク!\nユーは ベリー ストロング!\n$ティース キャント スタンダップ!\n歯が 立ちませーん!" } }, "magma_grunt": { "encounter": { - "1": "If you get in the way of Team Magma, don’t expect any mercy!", - "2": "You'd better not interfere with our plans! We're making the world a better place!", - "3": "You're in the way! Team Magma has no time for kids like you!", - "4": "I hope you brought marshmallows because things are about to heat up!", - "5": "We're going to use the power of a volcano! It's gonna be... explosive! Get it? Heh heh!" + "1": "おれたち マグマ団の 邪魔を するなら\n容赦は しないぜ!", + "2": "すべての 人々の ために\n我々 マグマ団は あるのよ!", + "3": "邪魔だ! マグマ団は お前の ような ヤツに\n用が ねーんだ! ほら さっさと 帰れよ!", + "4": "マシュマロ 持ってきた?\nすぐ 炎は 燃え上がっちゃう からー!!", + "5": "燃料の 力を 利用して 火山を 噴火させて やるのさ! ズドドーン とな!" }, "victory": { - "1": "Huh? I lost?!", - "2": "I can't believe I lost! I even skipped lunch for this", - "3": "No way! You're just a kid!", - "3_female": "No way! You're just a kid!", - "4": "Urrrgh... I should've ducked into our hideout right away...", - "5": "You beat me... Do you think the boss will dock my pay for this?" + "1": "ぬぬぬー 敗北 するなどー!?", + "2": "負けちゃった! せっかくの 昼ご飯を 抜いたのにー", + "3": "ガキの くせに この 強さ だと!?", + "3_female": "ガキの くせに この 強さ だと!?", + "4": "うぐぐ‥‥ 早く アジトに 逃げ込めば 良かった……", + "5": "負けた… そんなことじゃ ボーナスが 減るぜ?" } }, "aqua_grunt": { "encounter": { - "1": "No one who crosses Team Aqua gets any mercy, not even kids!", - "2": "Grrr... You've got some nerve meddling with Team Aqua!", - "3": "You're about to get soaked! And not just from my water Pokémon!", - "4": "We, Team Aqua, exist for the good of all!", - "5": "Prepare to be washed away by the tides of my... uh, Pokémon! Yeah, my Pokémon!" + "1": "おれら アクア団の ジャマするつもりかい?\nガキでも 容赦せーへんで!", + "2": "うむむ…… アクア団に 逆らうと いい度胸 だろ!", + "3": "おれの みずポケモンが おまれを ずぶ濡れに しちゃうぞ!", + "4": "活動を 邪魔する つもりなら ぶっ壊す!\nこの世界を より良い 場所に してるぞ!", + "5": "俺の ポケモンの 水の に 押し流されちゃうぞ!" }, "victory": { - "1": "You're kidding me!", - "2": "Arrgh, I didn't count on being meddled with by some meddling kid!", - "3": "I lost?! Guess I'll have to swim back to the hideout now...", - "4": "Oh, man, what a disaster... The boss is going to be furious...", - "5": "You beat me... Do you think the boss will make me walk the plank for this?" + "1": "冗談じゃねえ?!", + "2": "うむ‥‥ まさか ヤツに 邪魔 されるなんて\nこれっぽっちも おもってなかったぜ!", + "3": "チクショウ… アジトへ 泳ぎ帰らなくちゃ……", + "4": "……やばい! このまま じゃあ\nリーダーに 怒られちまうぞ……", + "5": "倒された… 板歩きの刑に なっちゃうかも……" } }, "galactic_grunt": { "encounter": { - "1": "Don't mess with Team Galactic!", - "2": "Witness the power of our technology and the future we envision!", - "3": "In the name of Team Galactic, I'll eliminate anyone who stands in our way!", - "4": "Get ready to lose!", - "5": "Hope you're ready for a cosmic beatdown!", - "5_female": "Hope you're ready for a cosmic beatdown!" + "1": "ギンガ団に 逆らうな!", + "2": "我らの 技術の 力と\n目論む 未来 を目に当たりに しちゃえ!", + "3": "ギンガ団にかわって 邪魔する 誰もを 排除するぞ!", + "4": "負ける 準備 いいかい!?", + "5": "コスミックに 破壊しちゃうぞ!\n覚悟せよ!", + "5_female": "コスミックに 破壊しちゃうぞ!\n覚悟せよ!" }, "victory": { - "1": "Shut down...", - "2": "This setback means nothing in the grand scheme.", - "3": "Our plans are bigger than this defeat.", - "4": "How?!", - "5": "Note to self: practice Pokémon battling, ASAP." + "1": "墜落……", + "2": "この負けは 次のカケで\n取り返せば いいさ!", + "3": "かまわない!\n我らの 目的の方は この負けより 大きい!", + "4": "ホワイ?! なぜ?!", + "5": "自分への覚え書き:\nポケモン勝負練習 なる早" } }, "plasma_grunt": { "encounter": { - "1": "We won't tolerate people who have different ideas!", - "2": "If I win against you, release your Pokémon!", - "3": "If you get in the way of Team Plasma, I'll take care of you!", - "4": "Team Plasma will liberate Pokémon from selfish humans like you!", - "5": "Our hairstyles are out of this world... but our battling skills? You'll find out soon enough." + "1": "あたしたちと 違う\n考えの 持ち主は 許さない!", + "2": "オレが 勝てば\nおまえの ポケモンを 解き放て!", + "3": "プラズマ団の ジャマをするなら\nアタシが やっつけてやる!!", + "4": "プラズマ団は お前のような 身勝手な\n人間から 開放する!", + "5": "おまえの ような トレーナーが\nポケモンを 苦しめているのだ!" }, "victory": { - "1": "Plasmaaaaaaaaa!", - "2": "How could I lose...", - "3": "...What a weak Pokémon, I'll just have to go steal some better ones!", - "4": "Great plans are always interrupted.", - "5": "This is bad... Badbadbadbadbadbadbad! Bad for Team Plasma! Or Plasbad, for short!" + "1": "プラズマーーーーー!!!", + "2": "なんてこと……! おれが 負けるなど!", + "3": "……弱い ポケモンね\n別の ポケモンを 奪わなきゃ!!", + "4": "すばらしい 計画に\nジャマは 付き物 ね!", + "5": "マズイ……\nマズイマズイマズイマズイマズイマズイ\n$プラズマ団と して マズイ\n縮めて プラズマズイ!" } }, "flare_grunt": { "encounter": { - "1": "Your Pokémon are no match for the elegance of Team Flare.", - "2": "Hope you brought your sunglasses, because things are about to get bright!", - "2_female": "Hope you brought your sunglasses, because things are about to get bright!", - "3": "Team Flare will cleanse the world of imperfection!", - "4": "Prepare to face the brilliance of Team Flare!", - "5": "Fashion is most important to us!" + "1": "お前の ポケモンが フレア団の 優美さには 敵わない!", + "2": "この サングラス いいだろう?\n羨ましいだろ? でも あげない!", + "2_female": "この サングラス いいだろう?\n羨ましいだろ? でも あげない!", + "3": "無風流は 許さない フレア団が お前を 打ち倒す!", + "4": "フレア団の 眩しさは お前を 圧倒するぞ!", + "5": "おれら 泣く子も 黙る\nオシャレチーム フレア団!" }, "victory": { - "1": "The future doesn't look bright for me.", - "2": "Perhaps there's more to battling than I thought. Back to the drawing board.", - "3": "Gahh?! I lost?!", - "4": "Even in defeat, Team Flare's elegance shines through.", - "5": "You may have beaten me, but when I lose, I go out in style!" + "1": "敗北でも フレア団が 優美さが 華やかに 輝く!", + "2": "目の前が 真っ暗……\nあっ サングラス だからか?", + "3": "ぐあああ! 負けた! シャレにならない!", + "4": "やっぱり バトルには\nオシャレより 大事なことが あるかも…", + "5": "ちっ! フレア団 御用達の\nオシャレスーツが 汚れたぜ!" } }, "aether_grunt": { "encounter": { - "1": "I'll fight you with all I have to wipe you out!", - "2": "I don't care if you're a kid or what. I'll send you flying if you threaten us!", - "2_female": "I don't care if you're a kid or what. I'll send you flying if you threaten us!", - "3": "I was told to turn away Trainers, whomever they might be!", - "4": "I'll show you the power of Aether Paradise!", - "5": "Now that you've learned of the darkness at the heart of Aether Paradise, we'll need you to conveniently disappear!" + "1": "侵入者 発見!\nシークレットラボを 守ります!", + "2": "大事な 研究なのよ!\n子供とはいえ ぶっとばすわよ!", + "2_female": "大事な 研究なのよ!\n子供とはいえ ぶっとばすわよ!", + "3": "どんな トレーナーだろうと\n追い返すよう いわれてるのよ!", + "4": "エーテルパラダイスの\n開発力を みせてやろう!", + "5": "エーテルパラダイスの 闇を 知った\nおまえには 消えてもらうぜ!" }, "victory": { - "1": "Hmph! You seem to have a lot of skill.", - "2": "What does this mean? What does this mean!", - "3": "Hey! You're so strong that there's no way I can turn you away!", - "4": "Hmm... It seems as though I may have lost.", - "5": "Here's an impression for you: Aiyee!" + "1": "ふむう…… どうやら\nわたしは 負けたようですな。", + "2": "どういう ことだ……!\nどういう ことだ……?", + "3": "ちょっと! 強すぎて\n追い返すなんて ムリムリ!", + "4": "ポケモン勝負に 関する\n発見は してなかった!", + "5": "真似して ぎゃひーん!" } }, "faba": { "encounter": { - "1": "I, Branch Chief Faba, shall show you the harshness of the real world!", - "2": "The man who is called Aether Paradise's last line of defense is to battle a mere child?", - "2_female": "The man who is called Aether Paradise's last line of defense is to battle a mere child?", - "3": "I, Faba, am the Aether Branch Chief. The only one in the world, I'm irreplaceable." + "1": "支部長 ザオボーは あなたに 現実を みせて さしあげますよ!", + "2": "ザオボー 人呼んで エーテルパラダイス\n最後の 最後の 最後の 砦は あなたを 壊します!", + "2_female": "ザオボー 人呼んで エーテルパラダイス\n最後の 最後の 最後の 砦は あなたを 壊します!", + "3": "エーテル財団の 支部長 といえば\n世界に ただ一人…… この ザオボーだけで ございます。" }, "victory": { - "1": "Aiyee!", - "2": "H-h-how can this be?! How could this child...", - "2_female": "H-h-how can this be?! How could this child...", - "3": "This is why... This is why I can't bring myself to like children." + "1": "ぎゃひーん!!!", + "2": "な ななな なんということでしょう?\nこの わたしが お子さま相手に……", + "2_female": "な ななな なんということでしょう?\nこの わたしが お子さま相手に……", + "3": "なんということでしょう!?" } }, "skull_grunt": { "encounter": { - "1": "We're not bad-we're just hard!", - "2": "You want some? That's how we say hello! Nice knowing you, punks!", - "2_female": "You want some? That's how we say hello! Nice knowing you, punks!", - "3": "We're just a bunch of guys and gals with a great interest in other people's Pokémon!", - "4": "Why you trying to act hard when we're already hard as bones out here, homie?", - "4_female": "Why you trying to act hard when we're already hard as bones out here, homie?", - "5": "Team Skull represent! We can't pay the rent! Had a lot of fun, but our youth was misspent!", - "5_female": "Team Skull represent! We can't pay the rent! Had a lot of fun, but our youth was misspent!" + "1": "まじめが キライでよ!\n$スカル団 やっているのに\nまじめに 下っ端 してるぜ?", + "2": "なんだ とは ご挨拶 ジャン!\nこいつら まとめて しめちゃおうよ!", + "2_female": "なんだ とは ご挨拶 ジャン!\nこいつら まとめて しめちゃおうよ!", + "3": "オレが ポケモンを 使いこなす\nすごいとこ みせてやりまスカ!", + "4": "あんたが ホネ身を 惜しまないかを 確かめたいから バトルッスカ!", + "4_female": "あんたが ホネ身を 惜しまないかを 確かめたいから バトルッスカ!", + "5": "だからYo♪ きけYo♪\n侵入者さんYo♪\n$オレら レペゼン サボる♪ スカル♪\nすこぶる♪ そそる♪ 話するYo♪", + "5_female": "だからYo♪ きけYo♪\n侵入者さんYo♪\n$オレら レペゼン サボる♪ スカル♪\nすこぶる♪ そそる♪ 話するYo♪" }, "victory": { - "1": "Huh? Is it over already?", - "2": "Time for us to break out, yo! Gotta tell y'all peace out, yo!", - "3": "We don't need your wack Pokémon anyway!", - "4": "Wha-?! This kid's way too strong-no bones about it!", - "5": "So, what? I'm lower than a Pokémon?! I already got self-esteem issues, man." + "1": "ちっ! まじめ だから\n……応援してやるよ\n$ヨーヨー 止まるなよ\n負けてもいいから 止まるなよ!", + "2": "しめるなよ! 袋叩き するなよ!", + "3": "オマエが ポケモンを 使いこなす\nすごいとこ みせられたのでスカ!?", + "4": "あんたらの ハート ホネ身に しみちゃう……!", + "5": "そーかYo♪\n後悔すんじゃねーYo♪" } }, "plumeria": { "encounter": { - "1": " ...Hmph. You don't look like anything special to me.", - "1_female": " ...Hmph. You don't look like anything special to me.", - "2": "It takes these dumb Grunts way too long to deal with you kids...", - "3": "Mess with anyone in Team Skull, and I'll show you how serious I can get." + "1": "あんたね…… さっき 聞いたの\n$……なんにも 感じない\nふつーのコに みえるけどねえ", + "1_female": " .あんたね…… さっき 聞いたの\n$……なんにも 感じない\nふつーのコに みえるけどねえ", + "2": "下っ端 あんたのような\n雑魚相手に モタモタ してるからさ", + "3": "スカル団を 束ねている……\n言うなれば あねごって ところ。\n$かわいい あいつらを\nいじめる あんたが ジャマなのよ!" }, "victory": { - "1": "Hmmph! You're pretty strong. I'll give you that.", - "1_female": "Hmmph! You're pretty strong. I'll give you that.", - "2": "Hmmph. Guess you are pretty tough. Now I understand why my Grunts waste so much time battling kids.", - "3": "Hmmph! I guess I just have to hold that loss." + "1": "ハンッ! たいした もんだよ\nただし 次 ジャマしたら 本気で やっちまうから", + "1_female": "ハンッ! たいした もんだよ\nただし 次 ジャマしたら 本気で やっちまうから", + "2": "あんた たいした もんだよ\nま 雑魚相手に 手間取るのも わかる 強さか。", + "3": "……チッ" } }, "macro_grunt": { "encounter": { - "1": "It looks like this is the end of the line for you!", - "2": "You are a trainer aren't you? I'm afraid that doesn't give you the right to interfere in our work.", - "2_female": "You are a trainer aren't you? I'm afraid that doesn't give you the right to interfere in our work.", - "3": "I'm from Macro Cosmos Insurance! Do you have a life insurance policy?" + "1": "不審者を 追い払って\nたんまり ボーナス いただくぜ!", + "2": "ジャマは 許しません!\n$マクロコスモスの さまざまな\n関連会社を 守るためにも 追い返します!", + "2_female": "ジャマは 許しません!\n$マクロコスモスの さまざまな\n関連会社を 守るためにも 追い返します!", + "3": "マクロコスモス生命です!\n保険に 入っていますか?" }, "victory": { - "1": "I have little choice but to respectfully retreat.", - "2": "Having to give up my pocket money... Losing means I'm back in the red...", - "3": "Nobody can beat Macro Cosmos when it comes to our dedication to our work!" + "1": "ボーナスが……\n夢の マイホームが……", + "2": "負けたからには\n素直に 引き下がりましょう。\n$だが ローズ委員長の\nジャマは しないでくださいよ。", + "3": "マクロコスモス生命の 仕事なら\n誰にも 負けないのに…" } }, "oleana": { "encounter": { - "1": "I won't let anyone interfere with Mr. Rose's plan!", - "2": "So, you got through all of the special staff that I had ordered to stop you. I would expect nothing less.", - "3": "For the chairman! I won't lose!" + "1": "ローズ様の ジャマ だなんて\nわたくし 絶対に 許せません!", + "2": "わたくしの オーダーを こなす 特別な\nスタッフ達を ものともせずに やってくるなんて……", + "3": "あなたを ボコボコに すれば\n委員長の 計画が すらっと 進めます!" }, "victory": { - "1": "*sigh* I wasn't able to win... Oleana...you really are a hopeless woman.", - "2": "Arghhh! This is inexcusable... What was I thinking... Any trainer who's made it this far would be no pushover..", - "2_female": "Arghhh! This is inexcusable... What was I thinking... Any trainer who's made it this far would be no pushover..", - "3": "*sigh* I am one tired Oleana..." + "1": "はあああぁ 勝てないなんて……\nオリーヴ…… ほんとに ダメな子", + "2": "はああ……! なんてこと……\n$勝ちあがった トレーナーの\n実力を みくびっていました……", + "2_female": "はああ……! なんてこと……\n$勝ちあがった トレーナーの\n実力を みくびっていました……", + "3": "まあ 生意気!\nオリーヴの パートナーを キズつけるなんて!" } }, "rocket_boss_giovanni_1": { "encounter": { - "1": "So! I must say, I am impressed you got here!" + "1": "こんな所 まで よく来た…" }, "victory": { - "1": "WHAT! This cannot be!" + "1": "ぐ ぐーッ! そんな ばかなーッ!" }, "defeat": { - "1": "Mark my words. Not being able to measure your own strength shows that you are still a child.", - "1_female": "Mark my words. Not being able to measure your own strength shows that you are still a child." + "1": "自分の 力を 把握できない 内は\nまだ 子供 ということだ…… 覚えておくがいい……", + "1_female": "自分の 力を 把握できない 内は\nまだ 子供 ということだ…… 覚えておくがいい……" } }, "rocket_boss_giovanni_2": { "encounter": { - "1": "My old associates need me... Are you going to get in my way?" + "1": "かつての 仲間たちが 私を 必要としてる…… 先の 失敗は もう 二度と 繰り返さない! " }, "victory": { - "1": "How is this possible...? The precious dream of Team Rocket has become little more than an illusion..." + "1": "なっ なぜだ……!\nロケット団 最高の 夢が 幻となって 消えていく……" }, "defeat": { - "1": "Team Rocket will be reborn again, and I will rule the world!" + "1": "ロケット団は 生まれ変わり\n世界を 我が物に するのだ!" } }, "magma_boss_maxie_1": { "encounter": { - "1": "I will bury you by my own hand. I hope you appreciate this honor!" + "1": "私 自らの 手で 葬ってやる……\n光栄に 思うが よい!" }, "victory": { - "1": "Ugh! You are... quite capable...\nI fell behind, but only by an inch..." + "1": "グッ…… やりおる……!\nわずか 1ミリ 及ばぬか……!" }, "defeat": { - "1": "Team Magma will prevail!" + "1": "マグマ団の 活動を 止めることなど 誰にも できぬ!" } }, "magma_boss_maxie_2": { "encounter": { - "1": "You are the final obstacle remaining between me and my goals.\n$Brace yourself for my ultimate attack! Fuhahaha!" + "1": "……キサマは 私が 図った日を\n迎えるための 最後の カベ―――\n$この マツブサが 治めし すべての チカラを もって 排除してやろう……!" }, "victory": { - "1": "This... This is not.. Ngh..." + "1": "こん…な……" }, "defeat": { - "1": "And now... I will transform this planet to a land ideal for humanity." + "1": "そして……\n$この世界は 人類にとって 理想の……" } }, "aqua_boss_archie_1": { "encounter": { - "1": "I'm the leader of Team Aqua, so I'm afraid it's the rope's end for you." + "1": "アクア団 リーダーとして テメエの ポケモン\nもろとも バッキバキに 揉み潰して やるよ!" }, "victory": { - "1": "Let's meet again somewhere. I'll be sure to remember that face." + "1": "んじゃあ またな\n……そのツラ 忘れねえぜ" }, "defeat": { - "1": "Brilliant! My team won't hold back now!" + "1": "おもしれえッ!\n今は アクア団が 全開を!" } }, "aqua_boss_archie_2": { "encounter": { - "1": "I've been waiting so long for this day to come.\nThis is the true power of my team!" + "1": "前回の勝負 じゃあ 見せられなかった\nポケモン達と オレの 全開パワー……\n$たーんと 食らわせてやるぜえああああッ!" }, "victory": { - "1": "Like I figured..." + "1": "……流石…だな…ッ!" }, "defeat": { - "1": "I'll return everything in this world to its original, pure state!!" + "1": "オレは この日が 来るのを 長い間 待っていた‥‥\n今は この世界を あるがままの 姿にッ!" } }, "galactic_boss_cyrus_1": { "encounter": { - "1": "You were compelled to come here by such vacuous sentimentality.\n$I will make you regret paying heed to your heart!", - "1_female": "You were compelled to come here by such vacuous sentimentality.\n$I will make you regret paying heed to your heart!" + "1": "心いう 不完全なものが 感じる\n哀れみや 優しさ……\n$そんな 曖昧なものに 突き動かされ \nここに来たことを わたしが 公開させてあげよう。", + "1_female": "心いう 不完全なものが 感じる\n哀れみや 優しさ……\n$そんな 曖昧なものに 突き動かされ \nここに来たことを わたしが 公開させてあげよう" }, "victory": { - "1": "Interesting. And quite curious." + "1": "面白い\nそして 興味深い" }, "defeat": { - "1": "I will create my new world..." + "1": "まさに 新しい ギンガの! 宇宙の 誕生だ!" } }, "galactic_boss_cyrus_2": { "encounter": { - "1": "So we meet again. It seems our fates have become intertwined.\n$But here and now, I will finally break that bond!" + "1": "またキミか。\n$キミとは ほとほと 縁が あるね…\n$腐れ縁 といっても いいが\n今 ここで 断ち切ろう!" }, "victory": { - "1": "How? How? HOW?!" + "1": "まさか まさか まさかッ!" }, "defeat": { - "1": "Farewell." + "1": "さらばだ。" } }, "plasma_boss_ghetsis_1": { @@ -2718,7 +2718,7 @@ }, "rival": { "encounter": { - "1": "@c{smile}あっ、ここに いたんだ! 旅に 出る前に 「じゃ またね!」って くらい 聞きたかったよ……$@c{smile_eclosed}やっぱり 夢を 追ってこうと しているんだ? 信じられない ほどね……$@c{serious_smile_fists}じゃあ、 ここまで 来たから バトルしよっか? 覚悟してるかを 確かめたい から!$@c{serious_mopen_fists}遠慮せずに 全力で かかってこいぜ!" + "1": "@c{smile}あっ、ここに いたんだ! 旅に 出る前に\n「じゃ またね!」って くらい 聞きたかったよ……$@c{smile_eclosed}やっぱり 夢を 追ってこうと しているんだ?\n信じられない ほどね……$@c{serious_smile_fists}じゃあ、 ここまで 来たから バトルしよっか?\n覚悟してるかを 確かめたい から!$@c{serious_mopen_fists}遠慮せずに 全力で かかってこいぜ!" }, "victory": { "1": "@c{shock}ウワッ、カンゼンに ぶっ壊したぜ。\n初心者だとは 思えないほど……$@c{smile}たぶん 運が良っかった だけが……\n最後まで 行ける素質が あるかもな!$こっちの アイテムを あげよう、 博士に そう言いつけたから。 結構 スゴそうな もんだ!$@c{serious_smile_fists}ここからも ガンバレ!" @@ -2734,7 +2734,7 @@ }, "rival_2": { "encounter": { - "1": "@c{smile}おや、なんと グウゼン。\n@c{smile_eclosed}今までも パーフェクトに 勝った ようだな……\n$@c{serious_mopen_fists}なんか 忍び寄った みたいだとは 分かるけど、 そんなことない… ほとんどはな。\n$@c{serious_smile_fists}ぶっちゃけ言うと、 オレが 負けた時から 再戦したくて ウズウズしてたぜ。\n$張り切って 特訓したから 今は ちゃんと 勢い 見せるんだ。\n$@c{serious_mopen_fists}今回も 遠慮しな!\n行こうぜ!" + "1": "@c{smile}おや、なんと グウゼン。\n@c{smile_eclosed}今までも パーフェクトに 勝った ようだな……\n$@c{serious_mopen_fists}なんか 忍び寄った みたいだとは 分かるけど、 そんなことない… ほとんどはな。\n$@c{serious_smile_fists}ぶっちゃけ言うと、 おれが 負けた時から 再戦したくて ウズウズしてたぜ。\n$張り切って 特訓したから 今は ちゃんと 勢い 見せるんだ。\n$@c{serious_mopen_fists}今回も 遠慮しな!\n行こうぜ!" }, "victory": { "1": "@c{neutral_eclosed}あ。 自信過剰かも。\n$@c{smile}いいけどさ、 こうなるのを 見込んだから。\n@c{serious_mopen_fists}次回まで もっと頑張らなくちゃ ってことだよな!\n\n$@c{smile}きっと 助け 要らないんだが、 もう一つの アイテムが 欲しいかと 思ったから あげるぜ。\n\n$@c{serious_smile_fists}でも これで ラストだ!\n相手に 利点を あげ続けると 行けないんだろう!" @@ -2753,10 +2753,10 @@ }, "rival_3": { "encounter": { - "1": "@c{smile}Hey, look who it is! It's been a while.\n@c{neutral}You're… still undefeated? Huh.\n$@c{neutral_eclosed}Things have been kind of… strange.\nIt's not the same back home without you.\n$@c{serious}I know it's selfish, but I need to get this off my chest.\n@c{neutral_eclosed}I think you're in over your head here.\n$@c{serious}Never losing once is just unrealistic.\nWe need to lose sometimes in order to grow.\n$@c{neutral_eclosed}You've had a great run but there's still so much ahead, and it only gets harder. @c{neutral}Are you prepared for that?\n$@c{serious_mopen_fists}If so, prove it to me." + "1": "@c{smile}おお 誰かと思ったら な! 久しぶり!\n@c{neutral}もう… 倒されなかった か? フン\n$@c{neutral_eclosed}最近 なんか… 変な 気分だな。\nキミが いないと ふるさとは 同じ場所 じゃない。\n$@c{serious}わがまま かもしれないが、本音 明かさなくちゃ。\n@c{neutral_eclosed}このままで すぐ キミの 手に 負えなくなる。\n$@c{serious}一回も 負けないこと って むちゃくちゃだろう。\nみんなは 時々 失敗しなくちゃ。 そうでなけりゃ 成長できない。\n$@c{neutral_eclosed}ここまで よく やって来たが、\nまだまだ 先が 辛いこと ばかり。@c{neutral}覚悟してるか?\n$@c{serious_mopen_fists}それなら、見せてくれ。" }, "victory": { - "1": "@c{angry_mhalf}This is ridiculous… I've hardly stopped training…\nHow are we still so far apart?" + "1": "@c{angry_mhalf}こりゃ むっちゃ だろ… 訓練しか してないよ…\nなぜ 力が もう こんなに 違うなだ?" } }, "rival_3_female": { @@ -2772,10 +2772,10 @@ }, "rival_4": { "encounter": { - "1": "@c{neutral}Hey.\n$I won't mince words or pleasantries with you.\n@c{neutral_eclosed}I'm here to win, plain and simple.\n$@c{serious_mhalf_fists}I've learned to maximize my potential by putting all my time into training.\n$@c{smile}You get a lot of extra time when you cut out the unnecessary sleep and social interaction.\n$@c{serious_mopen_fists}None of that matters anymore, not until I win.\n$@c{neutral_eclosed}I've even reached the point where I don't lose anymore.\n@c{smile_eclosed}I suppose your philosophy wasn't so wrong after all.\n$@c{angry_mhalf}Losing is for the weak, and I'm not weak anymore.\n$@c{serious_mopen_fists}Prepare yourself." + "1": "@c{neutral}よっ。\n$歯に 衣着せない。\n@c{neutral_eclosed}キミに 勝つために ここに 来た、それだけ。\n$@c{serious_mhalf_fists}地力を 最大限に 引き出す ために\n全労力を 費やして 訓練していた。\n$@c{smile}不要な 睡眠や 人間関係なんか 抜くと\n訓練の 時間は 割と 増えるね。\n$@c{serious_mopen_fists}そんなことは 勝てるときまで 全然 どうでもない。\n$@c{neutral_eclosed}今 負けられないとこまで やって来た。\n@c{smile_eclosed}キミの 考え方は 違いない ようだね。\n$@c{angry_mhalf}負けるのは 弱き者。 おれは もう 弱くない。\n$@c{serious_mopen_fists}覚悟せよ。" }, "victory": { - "1": "@c{neutral}What…@d{64} What are you?" + "1": "@c{neutral}一体…@d{64} 何モノか……?" } }, "rival_4_female": { @@ -2783,7 +2783,7 @@ "1": "@c{neutral}アタシよ! また 忘れちゃった… のね?\n$@c{smile}こんな 遠くまで 来たのは 鼻が高いことだよ! おめでと~\nしかし、 ここは 終着点だね。\n$@c{smile_eclosed}アタシの 中にある 全然 知らなかった 部分を 目覚めたよ。\n今は、 トレーニングしか してないみたい。\n$@c{smile_ehalf}食べたり 寝たりも しなくて 朝から晩まで ポケモンを 育って、 毎日 昨日より 強くなってる。\n$@c{neutral}実は… もう 自分 認識できない。\n$結局、 峠を越して まるで カミに なった。\n今は 誰にも アタシを 倒せないと 思う。\n$ねえ、分かる? 全ては アンタの お陰で。\n@c{smile_ehalf}お礼を言うか アンタのこと嫌いか どうしたらいいの 分からない。\n$@c{angry_mopen}覚悟しなさい。" }, "victory": { - "1": "@c{neutral}一体…@d{64} 何モノか…?" + "1": "@c{neutral}一体…@d{64} 何モノか……?" }, "defeat": { "1": "$@c{smile}ここまで 頑張ってたのを 誇りに思ってね。" @@ -2810,10 +2810,10 @@ }, "rival_6": { "encounter": { - "1": "@c{smile_eclosed}We meet again.\n$@c{neutral}I've had some time to reflect on all this.\nThere's a reason this all seems so strange.\n$@c{neutral_eclosed}Your dream, my drive to beat you…\nIt's all a part of something greater.\n$@c{serious}This isn't about me, or about you… This is about the world, @c{serious_mhalf_fists}and it's my purpose to push you to your limits.\n$@c{neutral_eclosed}Whether I've fulfilled that purpose I can't say, but I've done everything in my power.\n$@c{neutral}This place we ended up in is terrifying… Yet somehow I feel unphased, like I've been here before.\n$@c{serious_mhalf_fists}You feel the same, don't you?\n$@c{serious}…and it's like something here is speaking to me.\nThis is all the world's known for a long time now.\n$Those times we cherished together that seem so recent are nothing but a distant memory.\n$@c{neutral_eclosed}Who can say whether they were ever even real in the first place.\n$@c{serious_mopen_fists}You need to keep pushing, because if you don't, it will never end. You're the only one who can do this.\n$@c{serious_smile_fists}I hardly know what any of this means, I just know that it's true.\n$@c{serious_mopen_fists}If you can't defeat me here and now, you won't stand a chance." + "1": "@c{smile_eclosed}また 会ったね。\n$@c{neutral}今までの ことを 振り返る 時間があった。\n全てが 変に感じる 訳が あるよ。\n$@c{neutral_eclosed}キミの夢、おれが キミに 倒したい熱心……\nより大きい 何かの 部分だけだ。\n$@c{serious}おれや キミの 物語じゃない。これは 全世界の物語だ。\n@c{serious_mhalf_fists}この物語で、 おれの「役割」 っていうのは キミを 限界まで 押すこと。\n$@c{neutral_eclosed}その役割を 果たしたのかは 言えないが、 全力を尽くした。\n$@c{neutral}最後に行き着いた この場所って 恐ろしいが……\n以前 ここに来た ことがある ような 気がして、 怯えもしない。\n$@c{serious_mhalf_fists}キミも 同じ 気がするん だろう?\n$@c{serious}……何かが おれに 話してるようだ。\n昔から これだけしかは この世界こそ そのもの。\n$大事にしてた 最近だと思ってた 一緒にいた日々、\n今は もう 遠い記憶 だけだ。\n$@c{neutral_eclosed}そもそも 現実だったかは もう 言えなくなったな。\n$@c{serious_mopen_fists}キミは 頑張り続かないと、\n決して 終わらない。 キミしか できやしない。\n$@c{serious_smile_fists}全ての意味、 全然 分からない。\nしかし、 真実だと 知ってるな。\n$@c{serious_mopen_fists}今ここで おれを 倒せなきゃ、 最後に 勝ち目が ナイ。" }, "victory": { - "1": "@c{smile_eclosed}It looks like my work is done here.\n$I want you to promise me one thing.\n@c{smile}After you heal the world, please come home." + "1": "@c{smile_eclosed}おれの 仕事が 終わったようだな。\n$一つだけの ことを 約束してほしい。\n@c{smile}この世界を 癒やした後、 お願い…… 帰ってくれ。" } }, "rival_6_female": { diff --git a/src/locales/ja/menu.json b/src/locales/ja/menu.json index a3f725c3308..42de861fd07 100644 --- a/src/locales/ja/menu.json +++ b/src/locales/ja/menu.json @@ -6,7 +6,7 @@ "newGame": "はじめから", "settings": "設定", "selectGameMode": "ゲームモードを 選んでください。", - "logInOrCreateAccount": "始めるには、ログイン、または 登録して ください。\nメールアドレスは 必要が ありません!", + "logInOrCreateAccount": "始めるには、ログイン、または 登録して ください。\nメールアドレスは 必要 ありません!", "username": "ユーザー名", "password": "パスワード", "login": "ログイン", @@ -14,7 +14,7 @@ "register": "登録", "emptyUsername": "ユーザー名を 空にする ことは できません", "invalidLoginUsername": "入力されたユーザー名は無効です", - "invalidRegisterUsername": "ユーザー名には 英文字、 数字、 アンダースコアのみを 含くむ必要が あります", + "invalidRegisterUsername": "ユーザー名には 英文字、 数字、 アンダースコアのみを 含くむことが 必要です", "invalidLoginPassword": "入力したパスワードは無効です", "invalidRegisterPassword": "パスワードは 6文字以上 でなければなりません", "usernameAlreadyUsed": "入力したユーザー名は すでに 使用されています", diff --git a/src/locales/ja/splash-messages.json b/src/locales/ja/splash-messages.json index b7378e7a916..db3948fa2f1 100644 --- a/src/locales/ja/splash-messages.json +++ b/src/locales/ja/splash-messages.json @@ -1,5 +1,5 @@ { - "battlesWon": "Battles Won!", + "battlesWon": "勝ったバトル:{{count, number}}回!", "joinTheDiscord": "Join the Discord!", "infiniteLevels": "Infinite Levels!", "everythingStacks": "Everything Stacks!", diff --git a/src/locales/ko/splash-messages.json b/src/locales/ko/splash-messages.json index 6cf7ce050b7..1e89713ccde 100644 --- a/src/locales/ko/splash-messages.json +++ b/src/locales/ko/splash-messages.json @@ -1,5 +1,5 @@ { - "battlesWon": "전투에서 승리하세요!", + "battlesWon": "{{count, number}} 전투에서 승리하세요!", "joinTheDiscord": "디스코드에 가입하세요!", "infiniteLevels": "무한한 레벨!", "everythingStacks": "모든 것이 누적됩니다!", diff --git a/src/locales/pt_BR/splash-messages.json b/src/locales/pt_BR/splash-messages.json index 55c0b1b9e74..237b0f21202 100644 --- a/src/locales/pt_BR/splash-messages.json +++ b/src/locales/pt_BR/splash-messages.json @@ -1,5 +1,5 @@ { - "battlesWon": "Batalhas Ganhas!", + "battlesWon": "{{count, number}} Batalhas Ganhas!", "joinTheDiscord": "Junte-se ao Discord!", "infiniteLevels": "Níveis Infinitos!", "everythingStacks": "Tudo Acumula!", diff --git a/src/locales/zh_CN/splash-messages.json b/src/locales/zh_CN/splash-messages.json index 4d2d208edfd..24981513afe 100644 --- a/src/locales/zh_CN/splash-messages.json +++ b/src/locales/zh_CN/splash-messages.json @@ -1,5 +1,5 @@ { - "battlesWon": "场胜利!", + "battlesWon": "{{count, number}} 场胜利!", "joinTheDiscord": "加入Discord!", "infiniteLevels": "等级无限!", "everythingStacks": "道具全部叠加!", diff --git a/src/locales/zh_TW/splash-messages.json b/src/locales/zh_TW/splash-messages.json index a25e7dab97b..60b03549c2f 100644 --- a/src/locales/zh_TW/splash-messages.json +++ b/src/locales/zh_TW/splash-messages.json @@ -1,5 +1,5 @@ { - "battlesWon": "勝利場數!", + "battlesWon": "{{count, number}} 勝利場數!", "joinTheDiscord": "加入Discord!", "infiniteLevels": "無限等級!", "everythingStacks": "所有效果都能疊加!", diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index a23a9c5ece2..ce2ffc6a462 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1761,7 +1761,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), - new WeightedModifierType(modifierTypes.LOCK_CAPSULE, skipInLastClassicWaveOrDefault(3)), + new WeightedModifierType(modifierTypes.LOCK_CAPSULE, (party: Pokemon[]) => party[0].scene.gameMode.isClassic ? 0 : 3), new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)), new WeightedModifierType(modifierTypes.RARE_FORM_CHANGE_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 6, 24), new WeightedModifierType(modifierTypes.MEGA_BRACELET, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 9, 36), diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 0c4d2a63802..81a3f4f81cc 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -600,7 +600,7 @@ export class TerastallizeAccessModifier extends PersistentModifier { export abstract class PokemonHeldItemModifier extends PersistentModifier { public pokemonId: integer; - readonly isTransferrable: boolean = true; + public isTransferable: boolean = true; constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { super(type, stackCount); @@ -699,7 +699,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModifier { protected battlesLeft: integer; - readonly isTransferrable: boolean = false; + public isTransferable: boolean = false; constructor(type: ModifierTypes.ModifierType, pokemonId: integer, battlesLeft?: integer, stackCount?: integer) { super(type, pokemonId, stackCount); @@ -736,7 +736,7 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi export class TerastallizeModifier extends LapsingPokemonHeldItemModifier { public teraType: Type; - readonly isTransferrable: boolean = false; + public isTransferable: boolean = false; constructor(type: ModifierTypes.TerastallizeModifierType, pokemonId: integer, teraType: Type, battlesLeft?: integer, stackCount?: integer) { super(type, pokemonId, battlesLeft || 10, stackCount); @@ -799,7 +799,7 @@ export class TerastallizeModifier extends LapsingPokemonHeldItemModifier { */ export class BaseStatModifier extends PokemonHeldItemModifier { protected stat: PermanentStat; - readonly isTransferrable: boolean = false; + public isTransferable: boolean = false; constructor(type: ModifierType, pokemonId: integer, stat: PermanentStat, stackCount?: integer) { super(type, pokemonId, stackCount); @@ -843,7 +843,7 @@ export class BaseStatModifier extends PokemonHeldItemModifier { export class EvoTrackerModifier extends PokemonHeldItemModifier { protected species: Species; protected required: integer; - readonly isTransferrable: boolean = false; + public isTransferable: boolean = false; constructor(type: ModifierType, pokemonId: integer, species: Species, required: integer, stackCount?: integer) { super(type, pokemonId, stackCount); @@ -880,7 +880,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { */ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier { private statModifier: integer; - readonly isTransferrable: boolean = false; + public isTransferable: boolean = false; constructor(type: ModifierTypes.PokemonBaseStatTotalModifierType, pokemonId: integer, statModifier: integer, stackCount?: integer) { super(type, pokemonId, stackCount); @@ -929,7 +929,7 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier { export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier { private statModifier: integer; private stats: Stat[]; - readonly isTransferrable: boolean = false; + public isTransferable: boolean = false; constructor (type: ModifierType, pokemonId: integer, statModifier: integer, stats: Stat[], stackCount?: integer) { super(type, pokemonId, stackCount); @@ -979,7 +979,7 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier { * Currently used by Macho Brace item */ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { - readonly isTransferrable: boolean = false; + public isTransferable: boolean = false; constructor (type: ModifierType, pokemonId: integer, stackCount?: integer) { super(type, pokemonId, stackCount); @@ -2346,7 +2346,7 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier { export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier { public formChangeItem: FormChangeItem; public active: boolean; - readonly isTransferrable: boolean = false; + public isTransferable: boolean = false; constructor(type: ModifierTypes.FormChangeItemModifierType, pokemonId: integer, formChangeItem: FormChangeItem, active: boolean, stackCount?: integer) { super(type, pokemonId, stackCount); @@ -2691,7 +2691,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { const transferredModifierTypes: ModifierTypes.ModifierType[] = []; const itemModifiers = pokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === targetPokemon.id && m.isTransferrable, targetPokemon.isPlayer()) as PokemonHeldItemModifier[]; + && m.pokemonId === targetPokemon.id && m.isTransferable, targetPokemon.isPlayer()) as PokemonHeldItemModifier[]; let highestItemTier = itemModifiers.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is this bang correct? let tierItemModifiers = itemModifiers.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); @@ -2736,7 +2736,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { * @see {@linkcode modifierTypes[MINI_BLACK_HOLE]} */ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { - isTransferrable: boolean = true; + isTransferable: boolean = true; constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { super(type, pokemonId, stackCount); } @@ -2762,7 +2762,7 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { } setTransferrableFalse(): void { - this.isTransferrable = false; + this.isTransferable = false; } } diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 86e42acb26b..66e39cf98a5 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -16,6 +16,7 @@ import i18next from "i18next"; import { FieldPhase } from "./field-phase"; import { SelectTargetPhase } from "./select-target-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; +import { isNullOrUndefined } from "#app/utils"; export class CommandPhase extends FieldPhase { protected fieldIndex: integer; @@ -179,14 +180,16 @@ export class CommandPhase extends FieldPhase { case Command.POKEMON: case Command.RUN: const isSwitch = command === Command.POKEMON; - if (!isSwitch && this.scene.arena.biomeType === Biome.END) { + const { currentBattle, arena } = this.scene; + const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; + if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { this.scene.ui.showText("", 0); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); - } else if (!isSwitch && (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene.currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) { + } else if (!isSwitch && (currentBattle.battleType === BattleType.TRAINER || currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { @@ -197,12 +200,12 @@ export class CommandPhase extends FieldPhase { const batonPass = isSwitch && args[0] as boolean; const trappedAbMessages: string[] = []; if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch + currentBattle.turnCommands[this.fieldIndex] = isSwitch ? { command: Command.POKEMON, cursor: cursor, args: args } : { command: Command.RUN }; success = true; if (!isSwitch && this.fieldIndex) { - this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; + currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; } } else if (trappedAbMessages.length > 0) { if (!isSwitch) { @@ -219,7 +222,7 @@ export class CommandPhase extends FieldPhase { // trapTag should be defined at this point, but just in case... if (!trapTag) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch + currentBattle.turnCommands[this.fieldIndex] = isSwitch ? { command: Command.POKEMON, cursor: cursor, args: args } : { command: Command.RUN }; break; diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts index 65426846bb3..c251819f331 100644 --- a/src/phases/egg-lapse-phase.ts +++ b/src/phases/egg-lapse-phase.ts @@ -40,7 +40,7 @@ export class EggLapsePhase extends Phase { this.showSummary(); }, () => { this.hatchEggsRegular(eggsToHatch); - this.showSummary(); + this.end(); } ); }, 100, true); diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 8ab191324c6..e6ccca6c95a 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -60,6 +60,11 @@ export class GameOverPhase extends BattlePhase { this.scene.ui.fadeOut(1250).then(() => { this.scene.reset(); this.scene.clearPhaseQueue(); + // If this is a ME, clear any residual visual sprites before reloading + const encounter = this.scene.currentBattle.mysteryEncounter; + if (encounter?.introVisuals) { + this.scene.field.remove(encounter.introVisuals, true); + } this.scene.gameData.loadSession(this.scene, this.scene.sessionSlotId).then(() => { this.scene.pushPhase(new EncounterPhase(this.scene, true)); @@ -238,7 +243,7 @@ export class GameOverPhase extends BattlePhase { gameVersion: this.scene.game.config.gameVersion, timestamp: new Date().getTime(), challenges: this.scene.gameMode.challenges.map(c => new ChallengeData(c)), - mysteryEncounterType: this.scene.currentBattle.mysteryEncounter?.encounterType, + mysteryEncounterType: this.scene.currentBattle.mysteryEncounter?.encounterType ?? -1, mysteryEncounterSaveData: this.scene.mysteryEncounterSaveData } as SessionSaveData; } diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts index 2244980c899..1d953801178 100644 --- a/src/phases/message-phase.ts +++ b/src/phases/message-phase.ts @@ -6,14 +6,16 @@ export class MessagePhase extends Phase { private callbackDelay: integer | null; private prompt: boolean | null; private promptDelay: integer | null; + private speaker?: string; - constructor(scene: BattleScene, text: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) { + constructor(scene: BattleScene, text: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null, speaker?: string) { super(scene); this.text = text; this.callbackDelay = callbackDelay!; // TODO: is this bang correct? this.prompt = prompt!; // TODO: is this bang correct? this.promptDelay = promptDelay!; // TODO: is this bang correct? + this.speaker = speaker; } start() { @@ -21,11 +23,15 @@ export class MessagePhase extends Phase { if (this.text.indexOf("$") > -1) { const pageIndex = this.text.indexOf("$"); - this.scene.unshiftPhase(new MessagePhase(this.scene, this.text.slice(pageIndex + 1), this.callbackDelay, this.prompt, this.promptDelay)); + this.scene.unshiftPhase(new MessagePhase(this.scene, this.text.slice(pageIndex + 1), this.callbackDelay, this.prompt, this.promptDelay, this.speaker)); this.text = this.text.slice(0, pageIndex).trim(); } - this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt, this.promptDelay); + if (this.speaker) { + this.scene.ui.showDialogue(this.text, this.speaker, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.promptDelay ?? 0); + } else { + this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt, this.promptDelay); + } } end() { diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index e2fca951b2f..c3199166e84 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag"; import { MoveAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags"; -import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, DealsDoubleDamageToTagAttr } from "#app/data/move"; +import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr } from "#app/data/move"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Moves } from "#app/enums/moves"; @@ -394,7 +394,7 @@ export class MoveEffectPhase extends PokemonPhase { } const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); - if (semiInvulnerableTag && !this.move.getMove().getAttrs(DealsDoubleDamageToTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) { + if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) { return false; } diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 6c9d3fd8c1d..007b69650b9 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -3,7 +3,7 @@ import BattleScene from "../battle-scene"; import { Phase } from "../phase"; import { Mode } from "../ui/ui"; import { transitionMysteryEncounterIntroVisuals, OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils"; -import MysteryEncounterOption, { OptionPhaseCallback } from "../data/mystery-encounters/mystery-encounter-option"; +import MysteryEncounterOption, { OptionPhaseCallback } from "#app/data/mystery-encounters/mystery-encounter-option"; import { getCharVariantFromDialogue } from "../data/dialogue"; import { TrainerSlot } from "../data/trainer-config"; import { BattleSpec } from "#enums/battle-spec"; diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 39a0da1167f..58fb13ac466 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -1,7 +1,7 @@ import BattleScene from "#app/battle-scene"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { regenerateModifierPoolThresholds, ModifierTypeOption, ModifierType, getPlayerShopModifierTypeOptionsForWave, PokemonModifierType, FusePokemonModifierType, PokemonMoveModifierType, TmModifierType, RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions } from "#app/modifier/modifier-type"; -import { ExtraModifierModifier, Modifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { ExtraModifierModifier, HealShopCostModifier, Modifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; @@ -10,7 +10,7 @@ import * as Utils from "#app/utils"; import { BattlePhase } from "./battle-phase"; import Overrides from "#app/overrides"; import { CustomModifierSettings } from "#app/modifier/modifier-type"; -import { isNullOrUndefined } from "#app/utils"; +import { isNullOrUndefined, NumberHolder } from "#app/utils"; export class SelectModifierPhase extends BattlePhase { private rerollCount: integer; @@ -69,11 +69,11 @@ export class SelectModifierPhase extends BattlePhase { } let modifierType: ModifierType; let cost: integer; + const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers); switch (rowCursor) { case 0: switch (cursor) { case 0: - const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers); if (rerollCost < 0 || this.scene.money < rerollCost) { this.scene.ui.playError(); return false; @@ -94,7 +94,7 @@ export class SelectModifierPhase extends BattlePhase { this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.isTransferrable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; + && m.isTransferable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; const itemModifier = itemModifiers[itemIndex]; this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); } else { @@ -108,6 +108,11 @@ export class SelectModifierPhase extends BattlePhase { }); break; case 3: + if (rerollCost < 0) { + // Reroll lock button is also disabled when reroll is disabled + this.scene.ui.playError(); + return false; + } this.scene.lockModifierTiers = !this.scene.lockModifierTiers; const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); @@ -133,7 +138,10 @@ export class SelectModifierPhase extends BattlePhase { if (shopOption.type) { modifierType = shopOption.type; } - cost = shopOption.cost; + // Apply Black Sludge to healing item cost + const healingItemCost = new NumberHolder(shopOption.cost); + this.scene.applyModifier(HealShopCostModifier, true, healingItemCost); + cost = healingItemCost.value; break; } diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index c11dd80b3aa..c10adc5683d 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -1,6 +1,6 @@ import BattleScene from "#app/battle-scene"; -import { BattlerIndex, BattleType } from "#app/battle"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { BattlerIndex, BattleType, ClassicFixedBossWaves } from "#app/battle"; +import { CustomModifierSettings, modifierTypes } from "#app/modifier/modifier-type"; import { BattleEndPhase } from "./battle-end-phase"; import { NewBattlePhase } from "./new-battle-phase"; import { PokemonPhase } from "./pokemon-phase"; @@ -42,8 +42,12 @@ export class VictoryPhase extends PokemonPhase { } if (this.scene.gameMode.isEndless || !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { this.scene.pushPhase(new EggLapsePhase(this.scene)); + if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_BOSS_2) { + // Should get Lock Capsule on 165 before shop phase so it can be used in the rewards shop + this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.LOCK_CAPSULE)); + } if (this.scene.currentBattle.waveIndex % 10) { - this.scene.pushPhase(new SelectModifierPhase(this.scene)); + this.scene.pushPhase(new SelectModifierPhase(this.scene, undefined, undefined, this.getFixedBattleCustomModifiers())); } else if (this.scene.gameMode.isDaily) { this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_CHARM)); if (this.scene.currentBattle.waveIndex > 10 && !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { @@ -76,4 +80,18 @@ export class VictoryPhase extends PokemonPhase { this.end(); } + + /** + * If this wave is a fixed battle with special custom modifier rewards, + * will pass those settings to the upcoming {@linkcode SelectModifierPhase}`. + */ + getFixedBattleCustomModifiers(): CustomModifierSettings | undefined { + const gameMode = this.scene.gameMode; + const waveIndex = this.scene.currentBattle.waveIndex; + if (gameMode.isFixedBattle(waveIndex)) { + return gameMode.getFixedBattle(waveIndex).customModifierRewardSettings; + } + + return undefined; + } } diff --git a/src/system/achv.ts b/src/system/achv.ts index 6170fe23e1d..09ec74de50c 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -279,6 +279,8 @@ export function getAchievementDescription(localizationKey: string): string { return i18next.t("achv:FRESH_START.description", { context: genderStr }); case "INVERSE_BATTLE": return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr }); + case "BREEDERS_IN_SPACE": + return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr }); default: return ""; } @@ -356,6 +358,7 @@ export const achvs = { MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 18 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c, scene) => c instanceof FreshStartChallenge && c.value > 0 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, c => c instanceof InverseBattleChallenge && c.value > 0), + BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 100).setSecret(), }; export function initAchievements() { diff --git a/src/test/abilities/battle_bond.test.ts b/src/test/abilities/battle_bond.test.ts index 71e9438db8f..4882001cc8d 100644 --- a/src/test/abilities/battle_bond.test.ts +++ b/src/test/abilities/battle_bond.test.ts @@ -7,7 +7,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - BATTLE BOND", () => { let phaserGame: Phaser.Game; @@ -60,6 +60,5 @@ describe("Abilities - BATTLE BOND", () => { expect(greninja!.formIndex).toBe(baseForm); }, - TIMEOUT ); }); diff --git a/src/test/abilities/costar.test.ts b/src/test/abilities/costar.test.ts index 794bed0d3cf..2fd1cb26408 100644 --- a/src/test/abilities/costar.test.ts +++ b/src/test/abilities/costar.test.ts @@ -8,7 +8,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - COSTAR", () => { let phaserGame: Phaser.Game; @@ -59,7 +59,6 @@ describe("Abilities - COSTAR", () => { expect(leftPokemon.getStatStage(Stat.SPATK)).toBe(2); expect(rightPokemon.getStatStage(Stat.SPATK)).toBe(2); }, - TIMEOUT, ); test( @@ -83,6 +82,5 @@ describe("Abilities - COSTAR", () => { expect(leftPokemon.getStatStage(Stat.ATK)).toBe(-2); expect(rightPokemon.getStatStage(Stat.ATK)).toBe(-2); }, - TIMEOUT, ); }); diff --git a/src/test/abilities/dancer.test.ts b/src/test/abilities/dancer.test.ts index ec5ce53f4c3..7564a254dbe 100644 --- a/src/test/abilities/dancer.test.ts +++ b/src/test/abilities/dancer.test.ts @@ -7,7 +7,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - Dancer", () => { let phaserGame: Phaser.Game; @@ -60,5 +60,5 @@ describe("Abilities - Dancer", () => { // doesn't use PP if copied move is also in moveset expect(oricorio.moveset[0]?.ppUsed).toBe(0); - }, TIMEOUT); + }); }); diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts index fa7f26d2716..0268a738c0e 100644 --- a/src/test/abilities/disguise.test.ts +++ b/src/test/abilities/disguise.test.ts @@ -7,7 +7,7 @@ import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - Disguise", () => { let phaserGame: Phaser.Game; @@ -33,7 +33,7 @@ describe("Abilities - Disguise", () => { .enemyMoveset(Moves.SPLASH) .starterSpecies(Species.REGIELEKI) .moveset([Moves.SHADOW_SNEAK, Moves.VACUUM_WAVE, Moves.TOXIC_THREAD, Moves.SPLASH]); - }, TIMEOUT); + }); it("takes no damage from attacking move and transforms to Busted form, takes 1/8 max HP damage from the disguise breaking", async () => { await game.classicMode.startBattle(); @@ -50,7 +50,7 @@ describe("Abilities - Disguise", () => { expect(mimikyu.hp).equals(maxHp - disguiseDamage); expect(mimikyu.formIndex).toBe(bustedForm); - }, TIMEOUT); + }); it("doesn't break disguise when attacked with ineffective move", async () => { await game.classicMode.startBattle(); @@ -64,7 +64,7 @@ describe("Abilities - Disguise", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(mimikyu.formIndex).toBe(disguisedForm); - }, TIMEOUT); + }); it("takes no damage from the first hit of a multihit move and transforms to Busted form, then takes damage from the second hit", async () => { game.override.moveset([ Moves.SURGING_STRIKES ]); @@ -88,7 +88,7 @@ describe("Abilities - Disguise", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(mimikyu.hp).lessThan(maxHp - disguiseDamage); expect(mimikyu.formIndex).toBe(bustedForm); - }, TIMEOUT); + }); it("takes effects from status moves and damage from status effects", async () => { await game.classicMode.startBattle(); @@ -104,7 +104,7 @@ describe("Abilities - Disguise", () => { expect(mimikyu.status?.effect).toBe(StatusEffect.POISON); expect(mimikyu.getStatStage(Stat.SPD)).toBe(-1); expect(mimikyu.hp).toBeLessThan(mimikyu.getMaxHp()); - }, TIMEOUT); + }); it("persists form change when switched out", async () => { game.override.enemyMoveset([Moves.SHADOW_SNEAK]); @@ -129,7 +129,7 @@ describe("Abilities - Disguise", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(mimikyu.formIndex).toBe(bustedForm); - }, TIMEOUT); + }); it("persists form change when wave changes with no arena reset", async () => { game.override.starterSpecies(0); @@ -146,7 +146,7 @@ describe("Abilities - Disguise", () => { await game.toNextWave(); expect(mimikyu.formIndex).toBe(bustedForm); - }, TIMEOUT); + }); it("reverts to Disguised form on arena reset", async () => { game.override.startingWave(4); @@ -166,7 +166,7 @@ describe("Abilities - Disguise", () => { await game.toNextWave(); expect(mimikyu.formIndex).toBe(disguisedForm); - }, TIMEOUT); + }); it("reverts to Disguised form on biome change when fainted", async () => { game.override.startingWave(10); @@ -190,7 +190,7 @@ describe("Abilities - Disguise", () => { await game.phaseInterceptor.to("PartyHealPhase"); expect(mimikyu1.formIndex).toBe(disguisedForm); - }, TIMEOUT); + }); it("doesn't faint twice when fainting due to Disguise break damage, nor prevent faint from Disguise break damage if using Endure", async () => { game.override.enemyMoveset([Moves.ENDURE]); @@ -204,7 +204,7 @@ describe("Abilities - Disguise", () => { expect(game.scene.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); expect(game.scene.currentBattle.waveIndex).toBe(2); - }, TIMEOUT); + }); it("activates when Aerilate circumvents immunity to the move's base type", async () => { game.override.ability(Abilities.AERILATE); @@ -222,5 +222,5 @@ describe("Abilities - Disguise", () => { expect(mimikyu.formIndex).toBe(bustedForm); expect(mimikyu.hp).toBe(maxHp - disguiseDamage); - }, TIMEOUT); + }); }); diff --git a/src/test/abilities/galvanize.test.ts b/src/test/abilities/galvanize.test.ts index f81b854180a..1b7dde9ba60 100644 --- a/src/test/abilities/galvanize.test.ts +++ b/src/test/abilities/galvanize.test.ts @@ -9,7 +9,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - Galvanize", () => { let phaserGame: Phaser.Game; @@ -59,7 +59,7 @@ describe("Abilities - Galvanize", () => { expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.EFFECTIVE); expect(move.calculateBattlePower).toHaveReturnedWith(48); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); - }, TIMEOUT); + }); it("should cause Normal-type attacks to activate Volt Absorb", async () => { game.override.enemyAbility(Abilities.VOLT_ABSORB); @@ -81,7 +81,7 @@ describe("Abilities - Galvanize", () => { expect(playerPokemon.getMoveType).toHaveLastReturnedWith(Type.ELECTRIC); expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.NO_EFFECT); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); - }, TIMEOUT); + }); it("should not change the type of variable-type moves", async () => { game.override.enemySpecies(Species.MIGHTYENA); @@ -100,7 +100,7 @@ describe("Abilities - Galvanize", () => { expect(playerPokemon.getMoveType).not.toHaveLastReturnedWith(Type.ELECTRIC); expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.NO_EFFECT); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); - }, TIMEOUT); + }); it("should affect all hits of a Normal-type multi-hit move", async () => { await game.startBattle(); @@ -128,5 +128,5 @@ describe("Abilities - Galvanize", () => { } expect(enemyPokemon.apply).not.toHaveReturnedWith(HitResult.NO_EFFECT); - }, TIMEOUT); + }); }); diff --git a/src/test/abilities/gorilla_tactics.test.ts b/src/test/abilities/gorilla_tactics.test.ts index df698194323..5e92950526e 100644 --- a/src/test/abilities/gorilla_tactics.test.ts +++ b/src/test/abilities/gorilla_tactics.test.ts @@ -10,8 +10,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Abilities - Gorilla Tactics", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -49,7 +47,7 @@ describe("Abilities - Gorilla Tactics", () => { // Other moves should be restricted expect(darmanitan.isMoveRestricted(Moves.TACKLE)).toBe(true); expect(darmanitan.isMoveRestricted(Moves.SPLASH)).toBe(false); - }, TIMEOUT); + }); it("should struggle if the only usable move is disabled", async () => { await game.classicMode.startBattle([Species.GALAR_DARMANITAN]); @@ -79,5 +77,5 @@ describe("Abilities - Gorilla Tactics", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(darmanitan.hp).toBeLessThan(darmanitan.getMaxHp()); - }, TIMEOUT); + }); }); diff --git a/src/test/abilities/libero.test.ts b/src/test/abilities/libero.test.ts index 51f182d5401..f429d9ffc72 100644 --- a/src/test/abilities/libero.test.ts +++ b/src/test/abilities/libero.test.ts @@ -12,7 +12,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - Libero", () => { let phaserGame: Phaser.Game; @@ -52,7 +52,6 @@ describe("Abilities - Libero", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH); }, - TIMEOUT, ); test.skip( @@ -92,7 +91,6 @@ describe("Abilities - Libero", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH); }, - TIMEOUT, ); test( @@ -115,7 +113,6 @@ describe("Abilities - Libero", () => { moveType = Type[Type.FIRE]; expect(leadPokemonType).toBe(moveType); }, - TIMEOUT, ); test( @@ -138,7 +135,6 @@ describe("Abilities - Libero", () => { moveType = Type[Type.ICE]; expect(leadPokemonType).toBe(moveType); }, - TIMEOUT, ); test( @@ -157,7 +153,6 @@ describe("Abilities - Libero", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.AIR_SLASH); }, - TIMEOUT, ); test( @@ -175,7 +170,6 @@ describe("Abilities - Libero", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.DIG); }, - TIMEOUT, ); test( @@ -197,7 +191,6 @@ describe("Abilities - Libero", () => { expect(enemyPokemon.isFullHp()).toBe(true); testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE); }, - TIMEOUT, ); test( @@ -216,7 +209,6 @@ describe("Abilities - Libero", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE); }, - TIMEOUT, ); test( @@ -235,7 +227,6 @@ describe("Abilities - Libero", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE); }, - TIMEOUT, ); test( @@ -254,7 +245,6 @@ describe("Abilities - Libero", () => { expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO); }, - TIMEOUT, ); test( @@ -274,7 +264,6 @@ describe("Abilities - Libero", () => { expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO); }, - TIMEOUT, ); test( @@ -292,7 +281,6 @@ describe("Abilities - Libero", () => { expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO); }, - TIMEOUT, ); test( @@ -310,7 +298,6 @@ describe("Abilities - Libero", () => { expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO); }, - TIMEOUT, ); test( @@ -329,7 +316,6 @@ describe("Abilities - Libero", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TRICK_OR_TREAT); }, - TIMEOUT, ); test( @@ -348,7 +334,6 @@ describe("Abilities - Libero", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.CURSE); expect(leadPokemon.getTag(BattlerTagType.CURSED)).not.toBe(undefined); }, - TIMEOUT, ); }); diff --git a/src/test/abilities/magic_guard.test.ts b/src/test/abilities/magic_guard.test.ts index 4b3fb0ba985..dd8b83f7601 100644 --- a/src/test/abilities/magic_guard.test.ts +++ b/src/test/abilities/magic_guard.test.ts @@ -11,8 +11,6 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; // 20 sec timeout - describe("Abilities - Magic Guard", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -67,7 +65,7 @@ describe("Abilities - Magic Guard", () => { */ expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); it( @@ -91,7 +89,7 @@ describe("Abilities - Magic Guard", () => { */ expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(getStatusEffectCatchRateMultiplier(leadPokemon.status!.effect)).toBe(1.5); - }, TIMEOUT + } ); it( @@ -113,7 +111,7 @@ describe("Abilities - Magic Guard", () => { * - The player Pokemon (that just lost its Magic Guard ability) has taken damage from poison */ expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); @@ -138,7 +136,7 @@ describe("Abilities - Magic Guard", () => { */ expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(getStatusEffectCatchRateMultiplier(enemyPokemon.status!.effect)).toBe(1.5); - }, TIMEOUT + } ); it("Magic Guard prevents damage caused by toxic but other non-damaging effects are still applied", @@ -166,7 +164,7 @@ describe("Abilities - Magic Guard", () => { expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(enemyPokemon.status!.turnCount).toBeGreaterThan(toxicStartCounter); expect(getStatusEffectCatchRateMultiplier(enemyPokemon.status!.effect)).toBe(1.5); - }, TIMEOUT + } ); @@ -191,7 +189,7 @@ describe("Abilities - Magic Guard", () => { */ expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); it("Magic Guard does not prevent poison from Toxic Spikes", async () => { @@ -220,7 +218,7 @@ describe("Abilities - Magic Guard", () => { expect(enemyPokemon.status!.effect).toBe(StatusEffect.POISON); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); it("Magic Guard prevents against damage from volatile status effects", @@ -246,7 +244,7 @@ describe("Abilities - Magic Guard", () => { expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp()); expect(enemyPokemon.getTag(BattlerTagType.CURSED)).not.toBe(undefined); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); it("Magic Guard prevents crash damage", async () => { @@ -265,7 +263,7 @@ describe("Abilities - Magic Guard", () => { * - The player Pokemon (with Magic Guard) misses High Jump Kick but does not lose HP as a result */ expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); it("Magic Guard prevents damage from recoil", async () => { @@ -283,7 +281,7 @@ describe("Abilities - Magic Guard", () => { * - The player Pokemon (with Magic Guard) uses a recoil move but does not lose HP from recoil */ expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); it("Magic Guard does not prevent damage from Struggle's recoil", async () => { @@ -301,7 +299,7 @@ describe("Abilities - Magic Guard", () => { * - The player Pokemon (with Magic Guard) uses Struggle but does lose HP from Struggle's recoil */ expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); //This tests different move attributes than the recoil tests above @@ -320,7 +318,7 @@ describe("Abilities - Magic Guard", () => { * - The player Pokemon (with Magic Guard) uses a move with an HP cost but does not lose HP from using it */ expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); /* @@ -348,7 +346,7 @@ describe("Abilities - Magic Guard", () => { * - The player Pokemon (with Magic Guard) uses a non-attacking move with an HP cost and thus loses HP from using it */ expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); it("Magic Guard prevents damage from abilities with PostTurnHurtIfSleepingAbAttr", async () => { @@ -373,7 +371,7 @@ describe("Abilities - Magic Guard", () => { */ expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(leadPokemon.status!.effect).toBe(StatusEffect.SLEEP); - }, TIMEOUT + } ); it("Magic Guard prevents damage from abilities with PostFaintContactDamageAbAttr", async () => { @@ -398,7 +396,7 @@ describe("Abilities - Magic Guard", () => { */ expect(enemyPokemon.hp).toBe(0); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); it("Magic Guard prevents damage from abilities with PostDefendContactDamageAbAttr", async () => { @@ -422,7 +420,7 @@ describe("Abilities - Magic Guard", () => { */ expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); it("Magic Guard prevents damage from abilities with ReverseDrainAbAttr", async () => { @@ -446,7 +444,7 @@ describe("Abilities - Magic Guard", () => { */ expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); it("Magic Guard prevents HP loss from abilities with PostWeatherLapseDamageAbAttr", async () => { @@ -464,6 +462,6 @@ describe("Abilities - Magic Guard", () => { * - The player Pokemon (with Magic Guard) should not lose HP due to this ability attribute */ expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); }); diff --git a/src/test/abilities/parental_bond.test.ts b/src/test/abilities/parental_bond.test.ts index 2ad3f9e3f5c..22c9d8028be 100644 --- a/src/test/abilities/parental_bond.test.ts +++ b/src/test/abilities/parental_bond.test.ts @@ -10,7 +10,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - Parental Bond", () => { let phaserGame: Phaser.Game; @@ -62,7 +62,7 @@ describe("Abilities - Parental Bond", () => { expect(leadPokemon.turnData.hitCount).toBe(2); expect(secondStrikeDamage).toBe(toDmgValue(0.25 * firstStrikeDamage)); - }, TIMEOUT + } ); it( @@ -81,7 +81,7 @@ describe("Abilities - Parental Bond", () => { expect(leadPokemon.turnData.hitCount).toBe(2); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2); - }, TIMEOUT + } ); it( @@ -98,7 +98,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1); - }, TIMEOUT + } ); it( @@ -116,7 +116,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(leadPokemon.turnData.hitCount).toBe(2); - }, TIMEOUT + } ); it( @@ -133,7 +133,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("DamagePhase", false); expect(leadPokemon.turnData.hitCount).toBe(1); - }, TIMEOUT + } ); it( @@ -151,7 +151,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("DamagePhase", false); expect(leadPokemon.turnData.hitCount).toBe(1); - }, TIMEOUT + } ); it( @@ -167,7 +167,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - 80); - }, TIMEOUT + } ); it( @@ -189,7 +189,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - 4 * playerDamage); - }, TIMEOUT + } ); it( @@ -209,7 +209,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("BerryPhase", false); playerPokemon.forEach(p => expect(p.turnData.hitCount).toBe(1)); - }, TIMEOUT + } ); it( @@ -225,7 +225,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("DamagePhase", false); expect(leadPokemon.turnData.hitCount).toBe(2); - }, TIMEOUT + } ); it( @@ -247,7 +247,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(leadPokemon.hp).toBe(Math.ceil(leadPokemon.getMaxHp() / 2)); - }, TIMEOUT + } ); it( @@ -271,7 +271,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(leadPokemon.isOfType(Type.FIRE)).toBe(false); - }, TIMEOUT + } ); it( @@ -289,7 +289,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("DamagePhase"); expect(leadPokemon.turnData.hitCount).toBe(4); - }, TIMEOUT + } ); it( @@ -313,7 +313,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("MoveEndPhase", false); expect(enemyPokemon.hp).toBe(Math.ceil(enemyPokemon.getMaxHp() * 0.25)); - }, TIMEOUT + } ); it( @@ -339,7 +339,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("MoveEndPhase", false); expect(enemyPokemon.hp).toBe(enemyStartingHp - 200); - }, TIMEOUT + } ); it( @@ -362,7 +362,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(leadPokemon.getTag(BattlerTagType.RECHARGING)).toBeDefined(); - }, TIMEOUT + } ); it( @@ -389,7 +389,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined(); - }, TIMEOUT + } ); it( @@ -413,7 +413,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); - }, TIMEOUT + } ); it( @@ -433,7 +433,7 @@ describe("Abilities - Parental Bond", () => { // This will cause this test to time out if the switch was forced on the first hit. await game.phaseInterceptor.to("MoveEffectPhase", false); - }, TIMEOUT + } ); it( @@ -457,7 +457,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.status?.effect).toBeUndefined(); - }, TIMEOUT + } ); it( @@ -475,7 +475,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(-1); - }, TIMEOUT + } ); it( @@ -493,7 +493,7 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(1); - }, TIMEOUT + } ); it( @@ -519,6 +519,6 @@ describe("Abilities - Parental Bond", () => { await game.phaseInterceptor.to("BerryPhase", false); enemyPokemon.forEach((p, i) => expect(enemyStartingHp[i] - p.hp).toBe(2 * enemyFirstHitDamage[i])); - }, TIMEOUT + } ); }); diff --git a/src/test/abilities/power_construct.test.ts b/src/test/abilities/power_construct.test.ts index ec37bc96c2f..94cee82fb4a 100644 --- a/src/test/abilities/power_construct.test.ts +++ b/src/test/abilities/power_construct.test.ts @@ -7,7 +7,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - POWER CONSTRUCT", () => { let phaserGame: Phaser.Game; @@ -60,6 +60,5 @@ describe("Abilities - POWER CONSTRUCT", () => { expect(zygarde!.formIndex).toBe(baseForm); }, - TIMEOUT ); }); diff --git a/src/test/abilities/protean.test.ts b/src/test/abilities/protean.test.ts index 4be58a677a6..8479a293722 100644 --- a/src/test/abilities/protean.test.ts +++ b/src/test/abilities/protean.test.ts @@ -12,7 +12,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - Protean", () => { let phaserGame: Phaser.Game; @@ -52,7 +52,6 @@ describe("Abilities - Protean", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH); }, - TIMEOUT, ); test.skip( @@ -92,7 +91,6 @@ describe("Abilities - Protean", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH); }, - TIMEOUT, ); test( @@ -115,7 +113,6 @@ describe("Abilities - Protean", () => { moveType = Type[Type.FIRE]; expect(leadPokemonType).toBe(moveType); }, - TIMEOUT, ); test( @@ -138,7 +135,6 @@ describe("Abilities - Protean", () => { moveType = Type[Type.ICE]; expect(leadPokemonType).toBe(moveType); }, - TIMEOUT, ); test( @@ -157,7 +153,6 @@ describe("Abilities - Protean", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.AIR_SLASH); }, - TIMEOUT, ); test( @@ -175,7 +170,6 @@ describe("Abilities - Protean", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.DIG); }, - TIMEOUT, ); test( @@ -197,7 +191,6 @@ describe("Abilities - Protean", () => { expect(enemyPokemon.isFullHp()).toBe(true); testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE); }, - TIMEOUT, ); test( @@ -216,7 +209,6 @@ describe("Abilities - Protean", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE); }, - TIMEOUT, ); test( @@ -235,7 +227,6 @@ describe("Abilities - Protean", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE); }, - TIMEOUT, ); test( @@ -254,7 +245,6 @@ describe("Abilities - Protean", () => { expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN); }, - TIMEOUT, ); test( @@ -274,7 +264,6 @@ describe("Abilities - Protean", () => { expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN); }, - TIMEOUT, ); test( @@ -292,7 +281,6 @@ describe("Abilities - Protean", () => { expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN); }, - TIMEOUT, ); test( @@ -310,7 +298,6 @@ describe("Abilities - Protean", () => { expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN); }, - TIMEOUT, ); test( @@ -329,7 +316,6 @@ describe("Abilities - Protean", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TRICK_OR_TREAT); }, - TIMEOUT, ); test( @@ -348,7 +334,6 @@ describe("Abilities - Protean", () => { testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.CURSE); expect(leadPokemon.getTag(BattlerTagType.CURSED)).not.toBe(undefined); }, - TIMEOUT, ); }); diff --git a/src/test/abilities/quick_draw.test.ts b/src/test/abilities/quick_draw.test.ts index a02ee5cf56a..dbed6c822c4 100644 --- a/src/test/abilities/quick_draw.test.ts +++ b/src/test/abilities/quick_draw.test.ts @@ -55,7 +55,6 @@ describe("Abilities - Quick Draw", () => { }, 20000); test("does not triggered by non damage moves", { - timeout: 20000, retry: 5 }, async () => { await game.startBattle(); diff --git a/src/test/abilities/sand_veil.test.ts b/src/test/abilities/sand_veil.test.ts index da9fdcc01ab..201d8d89600 100644 --- a/src/test/abilities/sand_veil.test.ts +++ b/src/test/abilities/sand_veil.test.ts @@ -11,7 +11,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - Sand Veil", () => { let phaserGame: Phaser.Game; @@ -75,6 +75,6 @@ describe("Abilities - Sand Veil", () => { expect(leadPokemon[0].isFullHp()).toBe(true); expect(leadPokemon[1].hp).toBeLessThan(leadPokemon[1].getMaxHp()); - }, TIMEOUT + } ); }); diff --git a/src/test/abilities/schooling.test.ts b/src/test/abilities/schooling.test.ts index ad9663bf8e5..4c5a66a41b7 100644 --- a/src/test/abilities/schooling.test.ts +++ b/src/test/abilities/schooling.test.ts @@ -7,7 +7,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - SCHOOLING", () => { let phaserGame: Phaser.Game; @@ -60,6 +60,5 @@ describe("Abilities - SCHOOLING", () => { expect(wishiwashi.formIndex).toBe(soloForm); }, - TIMEOUT ); }); diff --git a/src/test/abilities/shields_down.test.ts b/src/test/abilities/shields_down.test.ts index 9bfec23ddf1..411c23fc652 100644 --- a/src/test/abilities/shields_down.test.ts +++ b/src/test/abilities/shields_down.test.ts @@ -7,7 +7,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - SHIELDS DOWN", () => { let phaserGame: Phaser.Game; @@ -60,6 +60,5 @@ describe("Abilities - SHIELDS DOWN", () => { expect(minior.formIndex).toBe(meteorForm); }, - TIMEOUT ); }); diff --git a/src/test/abilities/sturdy.test.ts b/src/test/abilities/sturdy.test.ts index dc9f774cc5b..c329d0830d3 100644 --- a/src/test/abilities/sturdy.test.ts +++ b/src/test/abilities/sturdy.test.ts @@ -8,7 +8,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - Sturdy", () => { let phaserGame: Phaser.Game; @@ -45,7 +45,6 @@ describe("Abilities - Sturdy", () => { await game.phaseInterceptor.to(MoveEndPhase); expect(game.scene.getEnemyParty()[0].hp).toBe(1); }, - TIMEOUT ); test( @@ -62,7 +61,6 @@ describe("Abilities - Sturdy", () => { expect(enemyPokemon.hp).toBe(0); expect(enemyPokemon.isFainted()).toBe(true); }, - TIMEOUT ); test( @@ -75,7 +73,6 @@ describe("Abilities - Sturdy", () => { const enemyPokemon: EnemyPokemon = game.scene.getEnemyParty()[0]; expect(enemyPokemon.isFullHp()).toBe(true); }, - TIMEOUT ); test( @@ -91,7 +88,6 @@ describe("Abilities - Sturdy", () => { expect(enemyPokemon.hp).toBe(0); expect(enemyPokemon.isFainted()).toBe(true); }, - TIMEOUT ); }); diff --git a/src/test/abilities/tera_shell.test.ts b/src/test/abilities/tera_shell.test.ts index 2826469f3bf..13df49136ca 100644 --- a/src/test/abilities/tera_shell.test.ts +++ b/src/test/abilities/tera_shell.test.ts @@ -6,8 +6,6 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 10 * 1000; // 10 second timeout - describe("Abilities - Tera Shell", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -54,7 +52,7 @@ describe("Abilities - Tera Shell", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(2); - }, TIMEOUT + } ); it( @@ -71,7 +69,7 @@ describe("Abilities - Tera Shell", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(0); - }, TIMEOUT + } ); it( @@ -88,7 +86,7 @@ describe("Abilities - Tera Shell", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(0.25); - }, TIMEOUT + } ); it( @@ -106,6 +104,6 @@ describe("Abilities - Tera Shell", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(playerPokemon.apply).toHaveLastReturnedWith(HitResult.EFFECTIVE); expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp() - 40); - }, TIMEOUT + } ); }); diff --git a/src/test/abilities/unseen_fist.test.ts b/src/test/abilities/unseen_fist.test.ts index 813880c7326..0f285abd98f 100644 --- a/src/test/abilities/unseen_fist.test.ts +++ b/src/test/abilities/unseen_fist.test.ts @@ -8,7 +8,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BerryPhase } from "#app/phases/berry-phase"; -const TIMEOUT = 20 * 1000; + describe("Abilities - Unseen Fist", () => { let phaserGame: Phaser.Game; @@ -37,13 +37,11 @@ describe("Abilities - Unseen Fist", () => { it( "should cause a contact move to ignore Protect", () => testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, true), - TIMEOUT ); it( "should not cause a non-contact move to ignore Protect", () => testUnseenFistHitResult(game, Moves.ABSORB, Moves.PROTECT, false), - TIMEOUT ); it( @@ -51,19 +49,17 @@ describe("Abilities - Unseen Fist", () => { () => { game.override.passiveAbility(Abilities.LONG_REACH); testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, false); - }, TIMEOUT + } ); it( "should cause a contact move to ignore Wide Guard", () => testUnseenFistHitResult(game, Moves.BREAKING_SWIPE, Moves.WIDE_GUARD, true), - TIMEOUT ); it( "should not cause a non-contact move to ignore Wide Guard", () => testUnseenFistHitResult(game, Moves.BULLDOZE, Moves.WIDE_GUARD, false), - TIMEOUT ); it( @@ -83,7 +79,7 @@ describe("Abilities - Unseen Fist", () => { expect(enemyPokemon.getTag(BattlerTagType.SUBSTITUTE)).toBeUndefined(); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); }); diff --git a/src/test/abilities/zen_mode.test.ts b/src/test/abilities/zen_mode.test.ts index fd378647184..c7cbd9014e0 100644 --- a/src/test/abilities/zen_mode.test.ts +++ b/src/test/abilities/zen_mode.test.ts @@ -19,7 +19,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import { Status, StatusEffect } from "#app/data/status-effect"; -const TIMEOUT = 20 * 1000; + describe("Abilities - ZEN MODE", () => { let phaserGame: Phaser.Game; @@ -67,7 +67,6 @@ describe("Abilities - ZEN MODE", () => { expect(game.scene.getParty()[0].hp).toBeLessThan(100); expect(game.scene.getParty()[0].formIndex).toBe(0); }, - TIMEOUT ); test( @@ -87,7 +86,6 @@ describe("Abilities - ZEN MODE", () => { expect(game.scene.getParty()[0].hp).not.toBe(100); expect(game.scene.getParty()[0].formIndex).not.toBe(0); }, - TIMEOUT ); test( @@ -125,7 +123,6 @@ describe("Abilities - ZEN MODE", () => { await game.phaseInterceptor.to(PostSummonPhase); expect(game.scene.getParty()[1].formIndex).toBe(1); }, - TIMEOUT ); test( @@ -156,6 +153,5 @@ describe("Abilities - ZEN MODE", () => { expect(darmanitan.formIndex).toBe(baseForm); }, - TIMEOUT ); }); diff --git a/src/test/abilities/zero_to_hero.test.ts b/src/test/abilities/zero_to_hero.test.ts index eafc32b4c79..a7f7c970218 100644 --- a/src/test/abilities/zero_to_hero.test.ts +++ b/src/test/abilities/zero_to_hero.test.ts @@ -7,7 +7,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Abilities - ZERO TO HERO", () => { let phaserGame: Phaser.Game; @@ -59,7 +59,7 @@ describe("Abilities - ZERO TO HERO", () => { expect(palafin1.formIndex).toBe(baseForm); expect(palafin2.formIndex).toBe(baseForm); - }, TIMEOUT); + }); it("should swap to Hero form when switching out during a battle", async () => { await game.startBattle([Species.PALAFIN, Species.FEEBAS]); @@ -70,7 +70,7 @@ describe("Abilities - ZERO TO HERO", () => { game.doSwitchPokemon(1); await game.phaseInterceptor.to(QuietFormChangePhase); expect(palafin.formIndex).toBe(heroForm); - }, TIMEOUT); + }); it("should not swap to Hero form if switching due to faint", async () => { await game.startBattle([Species.PALAFIN, Species.FEEBAS]); @@ -83,7 +83,7 @@ describe("Abilities - ZERO TO HERO", () => { game.doSelectPartyPokemon(1); await game.toNextTurn(); expect(palafin.formIndex).toBe(baseForm); - }, TIMEOUT); + }); it("should stay hero form if fainted and then revived", async () => { game.override.starterForms({ @@ -105,5 +105,5 @@ describe("Abilities - ZERO TO HERO", () => { await game.toNextTurn(); expect(palafin.formIndex).toBe(heroForm); - }, TIMEOUT); + }); }); diff --git a/src/test/arena/grassy_terrain.test.ts b/src/test/arena/grassy_terrain.test.ts index efb2539885d..01bbc778ded 100644 --- a/src/test/arena/grassy_terrain.test.ts +++ b/src/test/arena/grassy_terrain.test.ts @@ -9,8 +9,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite describe("Arena - Grassy Terrain", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -52,7 +50,7 @@ describe("Arena - Grassy Terrain", () => { await game.phaseInterceptor.to("BerryPhase"); expect(eq.calculateBattlePower).toHaveReturnedWith(50); - }, TIMEOUT); + }); it("Does not halve the damage of Earthquake if opponent is not grounded", async () => { await game.classicMode.startBattle([Species.NINJASK]); @@ -67,5 +65,5 @@ describe("Arena - Grassy Terrain", () => { await game.phaseInterceptor.to("BerryPhase"); expect(eq.calculateBattlePower).toHaveReturnedWith(100); - }, TIMEOUT); + }); }); diff --git a/src/test/battle/inverse_battle.test.ts b/src/test/battle/inverse_battle.test.ts index d808f71addb..01a0348e730 100644 --- a/src/test/battle/inverse_battle.test.ts +++ b/src/test/battle/inverse_battle.test.ts @@ -10,7 +10,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Inverse Battle", () => { let phaserGame: Phaser.Game; @@ -56,7 +56,7 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); - }, TIMEOUT); + }); it("2x effective types are 0.5x effective - Thunderbolt against Flying Type", async () => { game.override @@ -73,7 +73,7 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(0.5); - }, TIMEOUT); + }); it("0.5x effective types are 2x effective - Thunderbolt against Electric Type", async () => { game.override @@ -90,7 +90,7 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); - }, TIMEOUT); + }); it("Stealth Rock follows the inverse matchups - Stealth Rock against Charizard deals 1/32 of max HP", async () => { game.scene.arena.addTag(ArenaTagType.STEALTH_ROCK, 1, Moves.STEALTH_ROCK, 0); @@ -110,7 +110,7 @@ describe("Inverse Battle", () => { console.log("Charizard's max HP: " + maxHp, "Damage: " + damage_prediction, "Current HP: " + currentHp, "Expected HP: " + expectedHP); expect(currentHp).toBeGreaterThan(maxHp * 31 / 32 - 1); - }, TIMEOUT); + }); it("Freeze Dry is 2x effective against Water Type like other Ice type Move - Freeze Dry against Squirtle", async () => { game.override @@ -127,7 +127,7 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); - }, TIMEOUT); + }); it("Water Absorb should heal against water moves - Water Absorb against Water gun", async () => { game.override @@ -143,7 +143,7 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.hp).toBe(enemy.getMaxHp()); - }, TIMEOUT); + }); it("Fire type does not get burned - Will-O-Wisp against Charmander", async () => { game.override @@ -160,7 +160,7 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.status?.effect).not.toBe(StatusEffect.BURN); - }, TIMEOUT); + }); it("Electric type does not get paralyzed - Nuzzle against Pikachu", async () => { game.override @@ -177,7 +177,7 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.status?.effect).not.toBe(StatusEffect.PARALYSIS); - }, TIMEOUT); + }); it("Ground type is not immune to Thunder Wave - Thunder Wave against Sandshrew", async () => { game.override @@ -194,7 +194,7 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.status?.effect).toBe(StatusEffect.PARALYSIS); - }, TIMEOUT); + }); it("Anticipation should trigger on 2x effective moves - Anticipation against Thunderbolt", async () => { @@ -206,7 +206,7 @@ describe("Inverse Battle", () => { await game.challengeMode.startBattle(); expect(game.scene.getEnemyPokemon()?.summonData.abilitiesApplied[0]).toBe(Abilities.ANTICIPATION); - }, TIMEOUT); + }); it("Conversion 2 should change the type to the resistive type - Conversion 2 against Dragonite", async () => { game.override @@ -223,7 +223,7 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(player.getTypes()[0]).toBe(Type.DRAGON); - }, TIMEOUT); + }); it("Flying Press should be 0.25x effective against Grass + Dark Type - Flying Press against Meowscarada", async () => { game.override @@ -240,7 +240,7 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(0.25); - }, TIMEOUT); + }); it("Scrappy ability has no effect - Tackle against Ghost Type still 2x effective with Scrappy", async () => { game.override @@ -258,7 +258,7 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); - }, TIMEOUT); + }); it("FORESIGHT has no effect - Tackle against Ghost Type still 2x effective with Foresight", async () => { game.override @@ -279,5 +279,5 @@ describe("Inverse Battle", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); - }, TIMEOUT); + }); }); diff --git a/src/test/battlerTags/octolock.test.ts b/src/test/battlerTags/octolock.test.ts index 7b1f9264370..ebd92dc6401 100644 --- a/src/test/battlerTags/octolock.test.ts +++ b/src/test/battlerTags/octolock.test.ts @@ -10,7 +10,7 @@ vi.mock("#app/battle-scene.js"); describe("BattlerTag - OctolockTag", () => { describe("lapse behavior", () => { - it("unshifts a StatStageChangePhase with expected stat stage changes", { timeout: 10000 }, async () => { + it("unshifts a StatStageChangePhase with expected stat stage changes", async () => { const mockPokemon = { scene: new BattleScene(), getBattlerIndex: () => 0, @@ -30,11 +30,11 @@ describe("BattlerTag - OctolockTag", () => { }); }); - it ("traps its target (extends TrappedTag)", { timeout: 2000 }, async () => { + it ("traps its target (extends TrappedTag)", async () => { expect(new OctolockTag(1)).toBeInstanceOf(TrappedTag); }); - it("can be added to pokemon who are not octolocked", { timeout: 2000 }, async => { + it("can be added to pokemon who are not octolocked", async => { const mockPokemon = { getTag: vi.fn().mockReturnValue(undefined) as Pokemon["getTag"], } as Pokemon; @@ -47,7 +47,7 @@ describe("BattlerTag - OctolockTag", () => { expect(mockPokemon.getTag).toHaveBeenCalledWith(BattlerTagType.OCTOLOCK); }); - it("cannot be added to pokemon who are octolocked", { timeout: 2000 }, async => { + it("cannot be added to pokemon who are octolocked", async => { const mockPokemon = { getTag: vi.fn().mockReturnValue(new BattlerTag(null!, null!, null!, null!)) as Pokemon["getTag"], } as Pokemon; diff --git a/src/test/battlerTags/stockpiling.test.ts b/src/test/battlerTags/stockpiling.test.ts index e568016dfef..ae2528e7b5f 100644 --- a/src/test/battlerTags/stockpiling.test.ts +++ b/src/test/battlerTags/stockpiling.test.ts @@ -12,7 +12,7 @@ beforeEach(() => { describe("BattlerTag - StockpilingTag", () => { describe("onAdd", () => { - it("unshifts a StatStageChangePhase with expected stat stage changes on add", { timeout: 10000 }, async () => { + it("unshifts a StatStageChangePhase with expected stat stage changes on add", async () => { const mockPokemon = { scene: vi.mocked(new BattleScene()) as BattleScene, getBattlerIndex: () => 0, @@ -35,7 +35,7 @@ describe("BattlerTag - StockpilingTag", () => { expect(mockPokemon.scene.unshiftPhase).toBeCalledTimes(1); }); - it("unshifts a StatStageChangePhase with expected stat changes on add (one stat maxed)", { timeout: 10000 }, async () => { + it("unshifts a StatStageChangePhase with expected stat changes on add (one stat maxed)", async () => { const mockPokemon = { scene: new BattleScene(), summonData: new PokemonSummonData(), @@ -64,7 +64,7 @@ describe("BattlerTag - StockpilingTag", () => { }); describe("onOverlap", () => { - it("unshifts a StatStageChangePhase with expected stat changes on overlap", { timeout: 10000 }, async () => { + it("unshifts a StatStageChangePhase with expected stat changes on overlap", async () => { const mockPokemon = { scene: new BattleScene(), getBattlerIndex: () => 0, @@ -89,7 +89,7 @@ describe("BattlerTag - StockpilingTag", () => { }); describe("stack limit, stat tracking, and removal", () => { - it("can be added up to three times, even when one stat does not change", { timeout: 10000 }, async () => { + it("can be added up to three times, even when one stat does not change", async () => { const mockPokemon = { scene: new BattleScene(), summonData: new PokemonSummonData(), diff --git a/src/test/battlerTags/substitute.test.ts b/src/test/battlerTags/substitute.test.ts index 12888daca50..0802b549823 100644 --- a/src/test/battlerTags/substitute.test.ts +++ b/src/test/battlerTags/substitute.test.ts @@ -10,8 +10,6 @@ import { MoveEffectPhase } from "#app/phases/move-effect-phase"; vi.mock("#app/battle-scene.js"); -const TIMEOUT = 5 * 1000; // 5 sec timeout - describe("BattlerTag - SubstituteTag", () => { let mockPokemon: Pokemon; @@ -45,7 +43,7 @@ describe("BattlerTag - SubstituteTag", () => { subject.onAdd(mockPokemon); expect(subject.hp).toBe(25); - }, TIMEOUT + } ); it( @@ -67,7 +65,7 @@ describe("BattlerTag - SubstituteTag", () => { expect(subject.sourceInFocus).toBeFalsy(); expect(mockPokemon.scene.triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); expect(mockPokemon.scene.queueMessage).toHaveBeenCalledTimes(1); - }, TIMEOUT + } ); it( @@ -79,7 +77,7 @@ describe("BattlerTag - SubstituteTag", () => { subject.onAdd(mockPokemon); expect(mockPokemon.findAndRemoveTags).toHaveBeenCalledTimes(1); - }, TIMEOUT + } ); }); @@ -114,7 +112,7 @@ describe("BattlerTag - SubstituteTag", () => { expect(mockPokemon.scene.triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); expect(mockPokemon.scene.queueMessage).toHaveBeenCalledTimes(1); - }, TIMEOUT + } ); }); @@ -150,7 +148,7 @@ describe("BattlerTag - SubstituteTag", () => { expect(subject.sourceInFocus).toBeTruthy(); expect(mockPokemon.scene.triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); expect(mockPokemon.scene.queueMessage).not.toHaveBeenCalled(); - }, TIMEOUT + } ); it( @@ -172,7 +170,7 @@ describe("BattlerTag - SubstituteTag", () => { expect(subject.sourceInFocus).toBeFalsy(); expect(mockPokemon.scene.triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); expect(mockPokemon.scene.queueMessage).not.toHaveBeenCalled(); - }, TIMEOUT + } ); /** TODO: Figure out how to mock a MoveEffectPhase correctly for this test */ @@ -200,7 +198,7 @@ describe("BattlerTag - SubstituteTag", () => { expect(mockPokemon.scene.triggerPokemonBattleAnim).not.toHaveBeenCalled(); expect(mockPokemon.scene.queueMessage).toHaveBeenCalledTimes(1); - }, TIMEOUT + } ); it( @@ -212,7 +210,7 @@ describe("BattlerTag - SubstituteTag", () => { vi.spyOn(mockPokemon.scene, "queueMessage").mockReturnValue(); expect(subject.lapse(mockPokemon, BattlerTagLapseType.CUSTOM)).toBeFalsy(); - }, TIMEOUT + } ); it( diff --git a/src/test/boss-pokemon.test.ts b/src/test/boss-pokemon.test.ts index 8a0a0e01617..e316cac0cf6 100644 --- a/src/test/boss-pokemon.test.ts +++ b/src/test/boss-pokemon.test.ts @@ -9,8 +9,6 @@ import { EnemyPokemon } from "#app/field/pokemon"; import { toDmgValue } from "#app/utils"; describe("Boss Pokemon / Shields", () => { - const TIMEOUT = 20 * 1000; - let phaserGame: Phaser.Game; let game: GameManager; @@ -35,7 +33,7 @@ describe("Boss Pokemon / Shields", () => { .enemyMoveset(Moves.SPLASH) .enemyHeldItems([]) .startingLevel(1000) - .moveset([Moves.FALSE_SWIPE, Moves.SUPER_FANG, Moves.SPLASH]) + .moveset([Moves.FALSE_SWIPE, Moves.SUPER_FANG, Moves.SPLASH, Moves.PSYCHIC]) .ability(Abilities.NO_GUARD); }); @@ -62,7 +60,7 @@ describe("Boss Pokemon / Shields", () => { // Pokemon above level 100 get an extra shield level = 100; expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.RATTATA))).toBe(7); - }, TIMEOUT); + }); it("should reduce the number of shields if we are in a double battle", async () => { game.override @@ -77,7 +75,7 @@ describe("Boss Pokemon / Shields", () => { expect(boss1.bossSegments).toBe(2); expect(boss2.isBoss()).toBe(true); expect(boss2.bossSegments).toBe(2); - }, TIMEOUT); + }); it("shields should stop overflow damage and give stat stage boosts when broken", async () => { game.override.startingWave(150); // Floor 150 > 2 shields / 3 health segments @@ -107,7 +105,7 @@ describe("Boss Pokemon / Shields", () => { // Breaking the last shield gives a +2 boost to ATK, DEF, SP ATK, SP DEF or SPD expect(getTotalStatStageBoosts(enemyPokemon)).toBe(3); - }, TIMEOUT); + }); it("breaking multiple shields at once requires extra damage", async () => { game.override @@ -143,7 +141,7 @@ describe("Boss Pokemon / Shields", () => { expect(boss2.bossSegmentIndex).toBe(0); expect(boss2.hp).toBe(boss2.getMaxHp() - toDmgValue(boss2SegmentHp * 4)); - }, TIMEOUT); + }); it("the number of stat stage boosts is consistent when several shields are broken at once", async () => { const shieldsToBreak = 4; @@ -196,7 +194,29 @@ describe("Boss Pokemon / Shields", () => { await game.toNextTurn(); expect(getTotalStatStageBoosts(boss2)).toBe(totalStatStages); - }, TIMEOUT); + }); + + it("the boss enduring does not proc an extra stat boost", async () => { + game.override + .enemyHealthSegments(2) + .enemyAbility(Abilities.STURDY); + + await game.classicMode.startBattle([ Species.MEWTWO ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + expect(enemyPokemon.isBoss()).toBe(true); + expect(enemyPokemon.bossSegments).toBe(2); + expect(getTotalStatStageBoosts(enemyPokemon)).toBe(0); + + game.move.select(Moves.PSYCHIC); + await game.toNextTurn(); + + // Enemy survived with Sturdy + expect(enemyPokemon.bossSegmentIndex).toBe(0); + expect(enemyPokemon.hp).toBe(1); + expect(getTotalStatStageBoosts(enemyPokemon)).toBe(1); + + }); /** * Gets the sum of the effective stat stage boosts for the given Pokemon diff --git a/src/test/data/splash_messages.test.ts b/src/test/data/splash_messages.test.ts new file mode 100644 index 00000000000..7e07b9a6e77 --- /dev/null +++ b/src/test/data/splash_messages.test.ts @@ -0,0 +1,66 @@ +import { getSplashMessages } from "#app/data/splash-messages"; +import { describe, expect, it, vi, afterEach, beforeEach } from "vitest"; +import * as Constants from "#app/constants"; + +describe("Data - Splash Messages", () => { + it("should contain at least 15 splash messages", () => { + expect(getSplashMessages().length).toBeGreaterThanOrEqual(15); + }); + + // make sure to adjust this test if the weight it changed! + it("should add contain 10 `battlesWon` splash messages", () => { + const battlesWonMessages = getSplashMessages().filter((message) => message === "splashMessages:battlesWon"); + expect(battlesWonMessages).toHaveLength(10); + }); + + describe("Seasonal", () => { + beforeEach(() => { + vi.spyOn(Constants, "USE_SEASONAL_SPLASH_MESSAGES", "get").mockReturnValue(true); + }); + + afterEach(() => { + vi.useRealTimers(); // reset system time + }); + + it("should contain halloween messages from Sep 15 to Oct 31", () => { + testSeason(new Date("2024-09-15"), new Date("2024-10-31"), "halloween"); + }); + + it("should contain xmas messages from Dec 1 to Dec 26", () => { + testSeason(new Date("2024-12-01"), new Date("2024-12-26"), "xmas"); + }); + + it("should contain new years messages frm Jan 1 to Jan 31", () => { + testSeason(new Date("2024-01-01"), new Date("2024-01-31"), "newYears"); + }); + }); +}); + +/** + * Helpoer method to test seasonal messages + * @param startDate The seasons start date + * @param endDate The seasons end date + * @param prefix the splash message prefix (e.g. `newYears` or `xmas`) + */ +function testSeason(startDate: Date, endDate: Date, prefix: string) { + const filterFn = (message: string) => message.startsWith(`splashMessages:${prefix}.`); + + const beforeDate = new Date(startDate); + beforeDate.setDate(startDate.getDate() - 1); + + const afterDate = new Date(endDate); + afterDate.setDate(endDate.getDate() + 1); + + const dates: Date[] = [beforeDate, startDate, endDate, afterDate]; + const [before, start, end, after] = dates.map((date) => { + vi.setSystemTime(date); + console.log("System time set to", date); + const count = getSplashMessages().filter(filterFn).length; + return count; + }); + + expect(before).toBe(0); + expect(start).toBeGreaterThanOrEqual(10); // make sure to adjust if weight is changed! + expect(end).toBeGreaterThanOrEqual(10); // make sure to adjust if weight is changed! + expect(after).toBe(0); +} diff --git a/src/test/enemy_command.test.ts b/src/test/enemy_command.test.ts index 9a2caa56dfc..53cddc86efb 100644 --- a/src/test/enemy_command.test.ts +++ b/src/test/enemy_command.test.ts @@ -8,7 +8,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + const NUM_TRIALS = 300; type MoveChoiceSet = { [key: number]: number }; @@ -74,7 +74,7 @@ describe("Enemy Commands - Move Selection", () => { expect(moveChoices[mv.moveId]).toBe(0); } }); - }, TIMEOUT + } ); it( @@ -101,6 +101,6 @@ describe("Enemy Commands - Move Selection", () => { expect(moveChoices[mv.moveId]).toBe(0); } }); - }, TIMEOUT + } ); }); diff --git a/src/test/evolution.test.ts b/src/test/evolution.test.ts index 16922babd7c..07865d7e64a 100644 --- a/src/test/evolution.test.ts +++ b/src/test/evolution.test.ts @@ -10,7 +10,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite describe("Evolution", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 1000 * 20; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -46,7 +45,7 @@ describe("Evolution", () => { trapinch.evolve(pokemonEvolutions[Species.TRAPINCH][0], trapinch.getSpeciesForm()); expect(trapinch.abilityIndex).toBe(1); - }, TIMEOUT); + }); it("should keep same ability slot after evolving", async () => { await game.classicMode.runToSummon([Species.BULBASAUR, Species.CHARMANDER]); @@ -61,7 +60,7 @@ describe("Evolution", () => { charmander.evolve(pokemonEvolutions[Species.CHARMANDER][0], charmander.getSpeciesForm()); expect(charmander.abilityIndex).toBe(1); - }, TIMEOUT); + }); it("should handle illegal abilityIndex values", async () => { await game.classicMode.runToSummon([Species.SQUIRTLE]); @@ -71,7 +70,7 @@ describe("Evolution", () => { squirtle.evolve(pokemonEvolutions[Species.SQUIRTLE][0], squirtle.getSpeciesForm()); expect(squirtle.abilityIndex).toBe(0); - }, TIMEOUT); + }); it("should handle nincada's unique evolution", async () => { await game.classicMode.runToSummon([Species.NINCADA]); @@ -87,7 +86,7 @@ describe("Evolution", () => { expect(shedinja.abilityIndex).toBe(1); // Regression test for https://github.com/pagefaultgames/pokerogue/issues/3842 expect(shedinja.metBiome).toBe(-1); - }, TIMEOUT); + }); it("should set wild delay to NONE by default", () => { const speciesFormEvo = new SpeciesFormEvolution(Species.ABRA, null, null, 1000, null, null); @@ -120,7 +119,7 @@ describe("Evolution", () => { expect(totodile.hp).toBe(totodile.getMaxHp()); expect(totodile.hp).toBeGreaterThan(hpBefore); - }, TIMEOUT); + }); it("should not fully heal HP when evolving", async () => { game.override.moveset([Moves.SURF]) @@ -150,7 +149,7 @@ describe("Evolution", () => { expect(cyndaquil.getMaxHp()).toBeGreaterThan(maxHpBefore); expect(cyndaquil.hp).toBeGreaterThan(hpBefore); expect(cyndaquil.hp).toBeLessThan(cyndaquil.getMaxHp()); - }, TIMEOUT); + }); it("should handle rng-based split evolution", async () => { /* this test checks to make sure that tandemaus will @@ -174,5 +173,5 @@ describe("Evolution", () => { const fourForm = playerPokemon.getEvolution()!; expect(fourForm.evoFormKey).toBe(null); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is null } - }, TIMEOUT); + }); }); diff --git a/src/test/items/double_battle_chance_booster.test.ts b/src/test/items/double_battle_chance_booster.test.ts index 1d5051fa9e9..8d2bd7c9179 100644 --- a/src/test/items/double_battle_chance_booster.test.ts +++ b/src/test/items/double_battle_chance_booster.test.ts @@ -12,8 +12,6 @@ import { Button } from "#app/enums/buttons"; describe("Items - Double Battle Chance Boosters", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -39,7 +37,7 @@ describe("Items - Double Battle Chance Boosters", () => { await game.classicMode.startBattle(); expect(game.scene.getEnemyField().length).toBe(2); - }, TIMEOUT); + }); it("should guarantee double boss battle with 3 unique tiers", async () => { game.override @@ -57,7 +55,7 @@ describe("Items - Double Battle Chance Boosters", () => { expect(enemyField.length).toBe(2); expect(enemyField[0].isBoss()).toBe(true); expect(enemyField[1].isBoss()).toBe(true); - }, TIMEOUT); + }); it("should renew how many battles are left of existing booster when picking up new booster of same tier", async() => { game.override @@ -100,5 +98,5 @@ describe("Items - Double Battle Chance Boosters", () => { } } expect(count).toBe(1); - }, TIMEOUT); + }); }); diff --git a/src/test/items/eviolite.test.ts b/src/test/items/eviolite.test.ts index d9991d47a89..7b2f9a15fce 100644 --- a/src/test/items/eviolite.test.ts +++ b/src/test/items/eviolite.test.ts @@ -9,8 +9,6 @@ import { StatBoosterModifier } from "#app/modifier/modifier"; describe("Items - Eviolite", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phase.Game({ type: Phaser.HEADLESS, @@ -50,7 +48,7 @@ describe("Items - Eviolite", () => { expect(partyMember.getEffectiveStat(Stat.DEF)).toBe(Math.floor(defStat * 1.5)); expect(partyMember.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(spDefStat * 1.5)); - }, TIMEOUT); + }); it("should not provide a boost for fully evolved, unfused pokemon", async() => { await game.classicMode.startBattle([ @@ -74,7 +72,7 @@ describe("Items - Eviolite", () => { expect(partyMember.getEffectiveStat(Stat.DEF)).toBe(defStat); expect(partyMember.getEffectiveStat(Stat.SPDEF)).toBe(spDefStat); - }, TIMEOUT); + }); it("should provide 50% boost to DEF and SPDEF for completely unevolved, fused pokemon", async() => { await game.classicMode.startBattle([ @@ -107,7 +105,7 @@ describe("Items - Eviolite", () => { expect(partyMember.getEffectiveStat(Stat.DEF)).toBe(Math.floor(defStat * 1.5)); expect(partyMember.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(spDefStat * 1.5)); - }, TIMEOUT); + }); it("should provide 25% boost to DEF and SPDEF for partially unevolved (base), fused pokemon", async() => { await game.classicMode.startBattle([ @@ -140,7 +138,7 @@ describe("Items - Eviolite", () => { expect(partyMember.getEffectiveStat(Stat.DEF)).toBe(Math.floor(defStat * 1.25)); expect(partyMember.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(spDefStat * 1.25)); - }, TIMEOUT); + }); it("should provide 25% boost to DEF and SPDEF for partially unevolved (fusion), fused pokemon", async() => { await game.classicMode.startBattle([ @@ -173,7 +171,7 @@ describe("Items - Eviolite", () => { expect(partyMember.getEffectiveStat(Stat.DEF)).toBe(Math.floor(defStat * 1.25)); expect(partyMember.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(spDefStat * 1.25)); - }, TIMEOUT); + }); it("should not provide a boost for fully evolved, fused pokemon", async() => { await game.classicMode.startBattle([ @@ -206,7 +204,7 @@ describe("Items - Eviolite", () => { expect(partyMember.getEffectiveStat(Stat.DEF)).toBe(defStat); expect(partyMember.getEffectiveStat(Stat.SPDEF)).toBe(spDefStat); - }, TIMEOUT); + }); it("should not provide a boost for Gigantamax Pokémon", async() => { game.override.starterForms({ @@ -238,5 +236,5 @@ describe("Items - Eviolite", () => { expect(partyMember.getEffectiveStat(Stat.DEF)).toBe(defStat); expect(partyMember.getEffectiveStat(Stat.SPDEF)).toBe(spDefStat); - }, TIMEOUT); + }); }); diff --git a/src/test/items/grip_claw.test.ts b/src/test/items/grip_claw.test.ts index d9871616449..29d39cabc3e 100644 --- a/src/test/items/grip_claw.test.ts +++ b/src/test/items/grip_claw.test.ts @@ -9,7 +9,7 @@ import GameManager from "#test/utils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; // 20 seconds +// 20 seconds describe("Items - Grip Claw", () => { let phaserGame: Phaser.Game; @@ -63,6 +63,6 @@ describe("Items - Grip Claw", () => { await game.phaseInterceptor.to(MoveEndPhase, false); expect(enemyPokemon[1].getHeldItems.length).toBe(enemyHeldItemCt[1]); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/after_you.test.ts b/src/test/moves/after_you.test.ts index efce1b28a17..025b4804bf1 100644 --- a/src/test/moves/after_you.test.ts +++ b/src/test/moves/after_you.test.ts @@ -8,7 +8,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - After You", () => { let phaserGame: Phaser.Game; @@ -47,7 +47,7 @@ describe("Moves - After You", () => { const phase = game.scene.getCurrentPhase() as MovePhase; expect(phase.pokemon).toBe(game.scene.getPlayerField()[1]); await game.phaseInterceptor.to("MoveEndPhase"); - }, TIMEOUT); + }); it("fails if target already moved", async () => { game.override.enemySpecies(Species.SHUCKLE); @@ -61,5 +61,5 @@ describe("Moves - After You", () => { await game.phaseInterceptor.to(MovePhase); expect(game.scene.getPlayerField()[1].getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/alluring_voice.test.ts b/src/test/moves/alluring_voice.test.ts index b438d0f736a..3e86b46aa69 100644 --- a/src/test/moves/alluring_voice.test.ts +++ b/src/test/moves/alluring_voice.test.ts @@ -8,7 +8,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Alluring Voice", () => { let phaserGame: Phaser.Game; @@ -50,5 +50,5 @@ describe("Moves - Alluring Voice", () => { await game.phaseInterceptor.to(BerryPhase); expect(enemy.getTag(BattlerTagType.CONFUSED)?.tagType).toBe("CONFUSED"); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/astonish.test.ts b/src/test/moves/astonish.test.ts index b21e2a06051..694ad85803b 100644 --- a/src/test/moves/astonish.test.ts +++ b/src/test/moves/astonish.test.ts @@ -11,7 +11,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Astonish", () => { let phaserGame: Phaser.Game; @@ -67,6 +67,6 @@ describe("Moves - Astonish", () => { await game.phaseInterceptor.to(BerryPhase, false); expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/baddy_bad.test.ts b/src/test/moves/baddy_bad.test.ts index d1a221453a6..87a7e9e049d 100644 --- a/src/test/moves/baddy_bad.test.ts +++ b/src/test/moves/baddy_bad.test.ts @@ -8,8 +8,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Moves - Baddy Bad", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -39,5 +37,5 @@ describe("Moves - Baddy Bad", () => { await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.arena.tags.length).toBe(0); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/baneful_bunker.test.ts b/src/test/moves/baneful_bunker.test.ts index c4a3036565c..5f63e3b4313 100644 --- a/src/test/moves/baneful_bunker.test.ts +++ b/src/test/moves/baneful_bunker.test.ts @@ -7,7 +7,7 @@ import { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; import { StatusEffect } from "#app/enums/status-effect"; -const TIMEOUT = 20 * 1000; + describe("Moves - Baneful Bunker", () => { let phaserGame: Phaser.Game; @@ -50,7 +50,7 @@ describe("Moves - Baneful Bunker", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(leadPokemon.status?.effect === StatusEffect.POISON).toBeTruthy(); - }, TIMEOUT + } ); test( "should protect the user and poison attackers that make contact, regardless of accuracy checks", @@ -68,7 +68,7 @@ describe("Moves - Baneful Bunker", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(leadPokemon.status?.effect === StatusEffect.POISON).toBeTruthy(); - }, TIMEOUT + } ); test( @@ -88,6 +88,6 @@ describe("Moves - Baneful Bunker", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(leadPokemon.status?.effect === StatusEffect.POISON).toBeFalsy(); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/beak_blast.test.ts b/src/test/moves/beak_blast.test.ts index fe748c87826..3f4fe1d1d11 100644 --- a/src/test/moves/beak_blast.test.ts +++ b/src/test/moves/beak_blast.test.ts @@ -10,7 +10,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Beak Blast", () => { let phaserGame: Phaser.Game; @@ -54,7 +54,7 @@ describe("Moves - Beak Blast", () => { await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.status?.effect).toBe(StatusEffect.BURN); - }, TIMEOUT + } ); it( @@ -74,7 +74,7 @@ describe("Moves - Beak Blast", () => { await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.status?.effect).toBe(StatusEffect.BURN); - }, TIMEOUT + } ); it( @@ -94,7 +94,7 @@ describe("Moves - Beak Blast", () => { await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.status?.effect).not.toBe(StatusEffect.BURN); - }, TIMEOUT + } ); it( @@ -110,7 +110,7 @@ describe("Moves - Beak Blast", () => { await game.phaseInterceptor.to(BerryPhase, false); expect(leadPokemon.turnData.hitCount).toBe(2); - }, TIMEOUT + } ); it( @@ -131,6 +131,6 @@ describe("Moves - Beak Blast", () => { await game.phaseInterceptor.to(TurnEndPhase); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(leadPokemon.getTag(BattlerTagType.BEAK_BLAST_CHARGING)).toBeUndefined(); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/beat_up.test.ts b/src/test/moves/beat_up.test.ts index 70b33f56583..51ec768084c 100644 --- a/src/test/moves/beat_up.test.ts +++ b/src/test/moves/beat_up.test.ts @@ -7,8 +7,6 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; // 20 sec timeout - describe("Moves - Beat Up", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -57,7 +55,7 @@ describe("Moves - Beat Up", () => { await game.phaseInterceptor.to(MoveEffectPhase); expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); } - }, TIMEOUT + } ); it( @@ -74,7 +72,7 @@ describe("Moves - Beat Up", () => { await game.phaseInterceptor.to(MoveEffectPhase); expect(playerPokemon.turnData.hitCount).toBe(5); - }, TIMEOUT + } ); it( @@ -99,6 +97,6 @@ describe("Moves - Beat Up", () => { await game.phaseInterceptor.to(MoveEffectPhase); expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); } - }, TIMEOUT + } ); }); diff --git a/src/test/moves/belly_drum.test.ts b/src/test/moves/belly_drum.test.ts index 3d85c59a2a5..494272089e2 100644 --- a/src/test/moves/belly_drum.test.ts +++ b/src/test/moves/belly_drum.test.ts @@ -8,7 +8,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import { Abilities } from "#app/enums/abilities"; -const TIMEOUT = 20 * 1000; + // RATIO : HP Cost of Move const RATIO = 2; // PREDAMAGE : Amount of extra HP lost @@ -54,7 +54,7 @@ describe("Moves - BELLY DRUM", () => { expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6); - }, TIMEOUT + } ); test("will still take effect if an uninvolved stat stage is at max", @@ -74,7 +74,7 @@ describe("Moves - BELLY DRUM", () => { expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6); - }, TIMEOUT + } ); test("fails if the pokemon's ATK stat stage is at its maximum", @@ -90,7 +90,7 @@ describe("Moves - BELLY DRUM", () => { expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6); - }, TIMEOUT + } ); test("fails if the user's health is less than 1/2", @@ -106,6 +106,6 @@ describe("Moves - BELLY DRUM", () => { expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/burning_jealousy.test.ts b/src/test/moves/burning_jealousy.test.ts index 3f2bf453684..d6ebbf30bb1 100644 --- a/src/test/moves/burning_jealousy.test.ts +++ b/src/test/moves/burning_jealousy.test.ts @@ -8,7 +8,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Burning Jealousy", () => { let phaserGame: Phaser.Game; @@ -50,7 +50,7 @@ describe("Moves - Burning Jealousy", () => { await game.phaseInterceptor.to("BerryPhase"); expect(enemy.status?.effect).toBe(StatusEffect.BURN); - }, TIMEOUT); + }); it("should still burn the opponent if their stat stages were both raised and lowered in the same turn", async () => { game.override @@ -66,7 +66,7 @@ describe("Moves - Burning Jealousy", () => { await game.phaseInterceptor.to("BerryPhase"); expect(enemy.status?.effect).toBe(StatusEffect.BURN); - }, TIMEOUT); + }); it("should ignore stat stages raised by IMPOSTER", async () => { game.override @@ -81,11 +81,11 @@ describe("Moves - Burning Jealousy", () => { await game.phaseInterceptor.to("BerryPhase"); expect(enemy.status?.effect).toBeUndefined(); - }, TIMEOUT); + }); it.skip("should ignore weakness policy", async () => { // TODO: Make this test if WP is implemented await game.classicMode.startBattle(); - }, TIMEOUT); + }); it("should be boosted by Sheer Force even if opponent didn't raise stat stages", async () => { game.override @@ -98,5 +98,5 @@ describe("Moves - Burning Jealousy", () => { await game.phaseInterceptor.to("BerryPhase"); expect(allMoves[Moves.BURNING_JEALOUSY].calculateBattlePower).toHaveReturnedWith(allMoves[Moves.BURNING_JEALOUSY].power * 5461 / 4096); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/ceaseless_edge.test.ts b/src/test/moves/ceaseless_edge.test.ts index 8511b3179c6..e98fe462c62 100644 --- a/src/test/moves/ceaseless_edge.test.ts +++ b/src/test/moves/ceaseless_edge.test.ts @@ -10,7 +10,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Ceaseless Edge", () => { let phaserGame: Phaser.Game; @@ -61,7 +61,7 @@ describe("Moves - Ceaseless Edge", () => { expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); expect(tagAfter.layers).toBe(1); expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); - }, TIMEOUT + } ); test( @@ -86,7 +86,7 @@ describe("Moves - Ceaseless Edge", () => { expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); expect(tagAfter.layers).toBe(2); expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); - }, TIMEOUT + } ); test( @@ -114,6 +114,6 @@ describe("Moves - Ceaseless Edge", () => { game.move.select(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase, false); expect(game.scene.currentBattle.enemyParty[0].hp).toBeLessThan(hpBeforeSpikes); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/clangorous_soul.test.ts b/src/test/moves/clangorous_soul.test.ts index 015b73b4dab..8f0bfb2549f 100644 --- a/src/test/moves/clangorous_soul.test.ts +++ b/src/test/moves/clangorous_soul.test.ts @@ -6,7 +6,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { Stat } from "#enums/stat"; -const TIMEOUT = 20 * 1000; + /** HP Cost of Move */ const RATIO = 3; /** Amount of extra HP lost */ @@ -54,7 +54,7 @@ describe("Moves - Clangorous Soul", () => { expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(1); expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(1); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(1); - }, TIMEOUT + } ); it("will still take effect if one or more of the involved stat stages are not at max", @@ -79,7 +79,7 @@ describe("Moves - Clangorous Soul", () => { expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6); expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(5); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(1); - }, TIMEOUT + } ); it("fails if all stat stages involved are at max", @@ -103,7 +103,7 @@ describe("Moves - Clangorous Soul", () => { expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6); expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(6); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(6); - }, TIMEOUT + } ); it("fails if the user's health is less than 1/3", @@ -123,6 +123,6 @@ describe("Moves - Clangorous Soul", () => { expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(0); expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(0); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(0); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/crafty_shield.test.ts b/src/test/moves/crafty_shield.test.ts index 7b962518944..63399c3a86a 100644 --- a/src/test/moves/crafty_shield.test.ts +++ b/src/test/moves/crafty_shield.test.ts @@ -9,7 +9,7 @@ import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BerryPhase } from "#app/phases/berry-phase"; import { CommandPhase } from "#app/phases/command-phase"; -const TIMEOUT = 20 * 1000; + describe("Moves - Crafty Shield", () => { let phaserGame: Phaser.Game; @@ -56,7 +56,7 @@ describe("Moves - Crafty Shield", () => { await game.phaseInterceptor.to(BerryPhase, false); leadPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0)); - }, TIMEOUT + } ); test( @@ -77,7 +77,7 @@ describe("Moves - Crafty Shield", () => { await game.phaseInterceptor.to(BerryPhase, false); expect(leadPokemon.some(p => p.hp < p.getMaxHp())).toBeTruthy(); - }, TIMEOUT + } ); test( @@ -99,7 +99,7 @@ describe("Moves - Crafty Shield", () => { await game.phaseInterceptor.to(BerryPhase, false); leadPokemon.forEach(p => expect(p.getTag(BattlerTagType.CURSED)).toBeUndefined()); - }, TIMEOUT + } ); test( diff --git a/src/test/moves/dragon_cheer.test.ts b/src/test/moves/dragon_cheer.test.ts index 0fc389ccfb6..beaf6ddb520 100644 --- a/src/test/moves/dragon_cheer.test.ts +++ b/src/test/moves/dragon_cheer.test.ts @@ -10,8 +10,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite describe("Moves - Dragon Cheer", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -47,7 +45,7 @@ describe("Moves - Dragon Cheer", () => { // After Tackle await game.phaseInterceptor.to("TurnEndPhase"); expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender - }, TIMEOUT); + }); it("increases the user's Dragon-type allies' critical hit ratio by two stages", async () => { await game.classicMode.startBattle([Species.MAGIKARP, Species.DRAGONAIR]); @@ -64,7 +62,7 @@ describe("Moves - Dragon Cheer", () => { // After Tackle await game.phaseInterceptor.to("TurnEndPhase"); expect(enemy.getCritStage).toHaveReturnedWith(2); // getCritStage is called on defender - }, TIMEOUT); + }); it("applies the effect based on the allies' type upon use of the move, and do not change if the allies' type changes later in battle", async () => { await game.classicMode.startBattle([Species.DRAGONAIR, Species.MAGIKARP]); @@ -96,5 +94,5 @@ describe("Moves - Dragon Cheer", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender - }, TIMEOUT); + }); }); diff --git a/src/test/moves/dragon_tail.test.ts b/src/test/moves/dragon_tail.test.ts index e1af29b2db1..4b222a0c477 100644 --- a/src/test/moves/dragon_tail.test.ts +++ b/src/test/moves/dragon_tail.test.ts @@ -10,7 +10,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import GameManager from "../utils/gameManager"; -const TIMEOUT = 20 * 1000; + describe("Moves - Dragon Tail", () => { let phaserGame: Phaser.Game; @@ -50,12 +50,12 @@ describe("Moves - Dragon Tail", () => { await game.phaseInterceptor.to(BerryPhase); const isVisible = enemyPokemon.visible; - const hasFled = enemyPokemon.wildFlee; + const hasFled = enemyPokemon.switchOutStatus; expect(!isVisible && hasFled).toBe(true); // simply want to test that the game makes it this far without crashing await game.phaseInterceptor.to(BattleEndPhase); - }, TIMEOUT + } ); test( @@ -72,10 +72,10 @@ describe("Moves - Dragon Tail", () => { await game.phaseInterceptor.to(BerryPhase); const isVisible = enemyPokemon.visible; - const hasFled = enemyPokemon.wildFlee; + const hasFled = enemyPokemon.switchOutStatus; expect(!isVisible && hasFled).toBe(true); expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); test( @@ -97,9 +97,9 @@ describe("Moves - Dragon Tail", () => { await game.phaseInterceptor.to(TurnEndPhase); const isVisibleLead = enemyLeadPokemon.visible; - const hasFledLead = enemyLeadPokemon.wildFlee; + const hasFledLead = enemyLeadPokemon.switchOutStatus; const isVisibleSec = enemySecPokemon.visible; - const hasFledSec = enemySecPokemon.wildFlee; + const hasFledSec = enemySecPokemon.switchOutStatus; expect(!isVisibleLead && hasFledLead && isVisibleSec && !hasFledSec).toBe(true); expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp()); @@ -109,7 +109,7 @@ describe("Moves - Dragon Tail", () => { await game.phaseInterceptor.to(BerryPhase); expect(enemySecPokemon.hp).toBeLessThan(enemySecPokemon.getMaxHp()); - }, TIMEOUT + } ); test( @@ -133,14 +133,14 @@ describe("Moves - Dragon Tail", () => { await game.phaseInterceptor.to(BerryPhase); const isVisibleLead = enemyLeadPokemon.visible; - const hasFledLead = enemyLeadPokemon.wildFlee; + const hasFledLead = enemyLeadPokemon.switchOutStatus; const isVisibleSec = enemySecPokemon.visible; - const hasFledSec = enemySecPokemon.wildFlee; + const hasFledSec = enemySecPokemon.switchOutStatus; expect(!isVisibleLead && hasFledLead && !isVisibleSec && hasFledSec).toBe(true); expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp()); expect(secPokemon.hp).toBeLessThan(secPokemon.getMaxHp()); expect(enemyLeadPokemon.hp).toBeLessThan(enemyLeadPokemon.getMaxHp()); expect(enemySecPokemon.hp).toBeLessThan(enemySecPokemon.getMaxHp()); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/fillet_away.test.ts b/src/test/moves/fillet_away.test.ts index 68ace42c2ec..d8dd74a259c 100644 --- a/src/test/moves/fillet_away.test.ts +++ b/src/test/moves/fillet_away.test.ts @@ -7,7 +7,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + /** HP Cost of Move */ const RATIO = 2; /** Amount of extra HP lost */ @@ -53,7 +53,7 @@ describe("Moves - FILLET AWAY", () => { expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(2); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(2); - }, TIMEOUT + } ); test("still takes effect if one or more of the involved stat stages are not at max", @@ -74,7 +74,7 @@ describe("Moves - FILLET AWAY", () => { expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(5); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(2); - }, TIMEOUT + } ); test("fails if all stat stages involved are at max", @@ -94,7 +94,7 @@ describe("Moves - FILLET AWAY", () => { expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(6); - }, TIMEOUT + } ); test("fails if the user's health is less than 1/2", @@ -112,6 +112,6 @@ describe("Moves - FILLET AWAY", () => { expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(0); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(0); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/focus_punch.test.ts b/src/test/moves/focus_punch.test.ts index ca80c688169..b839c228b68 100644 --- a/src/test/moves/focus_punch.test.ts +++ b/src/test/moves/focus_punch.test.ts @@ -10,7 +10,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Focus Punch", () => { let phaserGame: Phaser.Game; @@ -61,7 +61,7 @@ describe("Moves - Focus Punch", () => { expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); expect(leadPokemon.getMoveHistory().length).toBe(1); expect(leadPokemon.turnData.damageDealt).toBe(enemyStartingHp - enemyPokemon.hp); - }, TIMEOUT + } ); it( @@ -88,7 +88,7 @@ describe("Moves - Focus Punch", () => { expect(enemyPokemon.hp).toBe(enemyStartingHp); expect(leadPokemon.getMoveHistory().length).toBe(1); expect(leadPokemon.turnData.damageDealt).toBe(0); - }, TIMEOUT + } ); it( @@ -111,7 +111,7 @@ describe("Moves - Focus Punch", () => { expect(leadPokemon.getMoveHistory().length).toBe(1); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); it( @@ -129,6 +129,6 @@ describe("Moves - Focus Punch", () => { expect(game.scene.getCurrentPhase() instanceof SwitchSummonPhase).toBeTruthy(); expect(game.scene.phaseQueue.find(phase => phase instanceof MoveHeaderPhase)).toBeDefined(); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/follow_me.test.ts b/src/test/moves/follow_me.test.ts index 7d0c4fdb546..28fb1045a8c 100644 --- a/src/test/moves/follow_me.test.ts +++ b/src/test/moves/follow_me.test.ts @@ -8,7 +8,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Follow Me", () => { let phaserGame: Phaser.Game; @@ -54,7 +54,7 @@ describe("Moves - Follow Me", () => { expect(playerPokemon[0].hp).toBeLessThan(playerPokemon[0].getMaxHp()); expect(playerPokemon[1].hp).toBe(playerPokemon[1].getMaxHp()); - }, TIMEOUT + } ); test( @@ -77,7 +77,7 @@ describe("Moves - Follow Me", () => { expect(playerPokemon[1].hp).toBeLessThan(playerPokemon[1].getMaxHp()); expect(playerPokemon[0].hp).toBe(playerPokemon[0].getMaxHp()); - }, TIMEOUT + } ); test( @@ -102,7 +102,7 @@ describe("Moves - Follow Me", () => { // If redirection was bypassed, both enemies should be damaged expect(enemyPokemon[0].hp).toBeLessThan(enemyPokemon[0].getMaxHp()); expect(enemyPokemon[1].hp).toBeLessThan(enemyPokemon[1].getMaxHp()); - }, TIMEOUT + } ); test( @@ -125,6 +125,6 @@ describe("Moves - Follow Me", () => { // If redirection was bypassed, both enemies should be damaged expect(enemyPokemon[0].hp).toBeLessThan(enemyPokemon[0].getMaxHp()); expect(enemyPokemon[1].hp).toBeLessThan(enemyPokemon[1].getMaxHp()); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/freeze_dry.test.ts b/src/test/moves/freeze_dry.test.ts index ff9e2f07162..b901f04e6a1 100644 --- a/src/test/moves/freeze_dry.test.ts +++ b/src/test/moves/freeze_dry.test.ts @@ -9,8 +9,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite describe("Moves - Freeze-Dry", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -44,7 +42,7 @@ describe("Moves - Freeze-Dry", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); - }, TIMEOUT); + }); it("should deal 4x damage to water/flying types", async () => { game.override.enemySpecies(Species.WINGULL); @@ -58,7 +56,7 @@ describe("Moves - Freeze-Dry", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(enemy.getMoveEffectiveness).toHaveReturnedWith(4); - }, TIMEOUT); + }); it("should deal 1x damage to water/fire types", async () => { game.override.enemySpecies(Species.VOLCANION); @@ -72,7 +70,7 @@ describe("Moves - Freeze-Dry", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(enemy.getMoveEffectiveness).toHaveReturnedWith(1); - }, TIMEOUT); + }); // enable if this is ever fixed (lol) it.todo("should deal 2x damage to water types under Normalize", async () => { @@ -87,7 +85,7 @@ describe("Moves - Freeze-Dry", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); - }, TIMEOUT); + }); // enable once Electrify is implemented (and the interaction is fixed, as above) it.todo("should deal 2x damage to water types under Electrify", async () => { @@ -102,5 +100,5 @@ describe("Moves - Freeze-Dry", () => { await game.phaseInterceptor.to("BerryPhase"); expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/gastro_acid.test.ts b/src/test/moves/gastro_acid.test.ts index cfc458a908f..60b2bd80c05 100644 --- a/src/test/moves/gastro_acid.test.ts +++ b/src/test/moves/gastro_acid.test.ts @@ -6,7 +6,7 @@ import { MoveResult } from "#app/field/pokemon"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Gastro Acid", () => { let phaserGame: Phaser.Game; @@ -60,7 +60,7 @@ describe("Moves - Gastro Acid", () => { expect(enemyField[0].hp).toBeLessThan(enemyField[0].getMaxHp()); expect(enemyField[1].isFullHp()).toBe(true); - }, TIMEOUT); + }); it("fails if used on an enemy with an already-suppressed ability", async () => { game.override.battleType(null); @@ -78,5 +78,5 @@ describe("Moves - Gastro Acid", () => { await game.phaseInterceptor.to("TurnInitPhase"); expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/glaive_rush.test.ts b/src/test/moves/glaive_rush.test.ts index 9eed6868432..1a524b4aef6 100644 --- a/src/test/moves/glaive_rush.test.ts +++ b/src/test/moves/glaive_rush.test.ts @@ -6,7 +6,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Glaive Rush", () => { let phaserGame: Phaser.Game; @@ -49,7 +49,7 @@ describe("Moves - Glaive Rush", () => { await game.phaseInterceptor.to("DamagePhase"); expect(enemy.hp).toBeLessThanOrEqual(1001 - (damageDealt * 3)); - }, TIMEOUT); + }); it("always gets hit by attacks", async () => { await game.classicMode.startBattle(); @@ -62,7 +62,7 @@ describe("Moves - Glaive Rush", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(enemy.hp).toBeLessThan(1000); - }, TIMEOUT); + }); it("interacts properly with multi-lens", async () => { game.override @@ -85,7 +85,7 @@ describe("Moves - Glaive Rush", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(player.hp).toBe(1000); - }, TIMEOUT); + }); it("secondary effects only last until next move", async () => { game.override.enemyMoveset([Moves.SHADOW_SNEAK]); @@ -111,7 +111,7 @@ describe("Moves - Glaive Rush", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(player.hp).toBe(damagedHp); - }, TIMEOUT); + }); it("secondary effects are removed upon switching", async () => { game.override @@ -135,7 +135,7 @@ describe("Moves - Glaive Rush", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(player.hp).toBe(player.getMaxHp()); - }, TIMEOUT); + }); it("secondary effects don't activate if move fails", async () => { game.override.moveset([Moves.SHADOW_SNEAK, Moves.PROTECT, Moves.SPLASH, Moves.GLAIVE_RUSH]); @@ -161,5 +161,5 @@ describe("Moves - Glaive Rush", () => { const damagedHP2 = 1000 - enemy.hp; expect(damagedHP2).toBeGreaterThanOrEqual((damagedHP1 * 2) - 1); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/guard_swap.test.ts b/src/test/moves/guard_swap.test.ts index 0c24f69c32c..99769b32899 100644 --- a/src/test/moves/guard_swap.test.ts +++ b/src/test/moves/guard_swap.test.ts @@ -11,8 +11,6 @@ import { MoveEndPhase } from "#app/phases/move-end-phase"; describe("Moves - Guard Swap", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -65,5 +63,5 @@ describe("Moves - Guard Swap", () => { expect(enemy.getStatStage(s)).toBe(1); } } - }, TIMEOUT); + }); }); diff --git a/src/test/moves/haze.test.ts b/src/test/moves/haze.test.ts index 211c1a41409..e5474801899 100644 --- a/src/test/moves/haze.test.ts +++ b/src/test/moves/haze.test.ts @@ -35,7 +35,7 @@ describe("Moves - Haze", () => { game.override.ability(Abilities.NONE); }); - it("should reset all stat changes of all Pokemon on field", { timeout: 10000 }, async () => { + it("should reset all stat changes of all Pokemon on field", async () => { await game.startBattle([Species.RATTATA]); const user = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; diff --git a/src/test/moves/heart_swap.test.ts b/src/test/moves/heart_swap.test.ts index f658641d46f..a128549c459 100644 --- a/src/test/moves/heart_swap.test.ts +++ b/src/test/moves/heart_swap.test.ts @@ -11,8 +11,6 @@ import { MoveEndPhase } from "#app/phases/move-end-phase"; describe("Moves - Heart Swap", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -60,5 +58,5 @@ describe("Moves - Heart Swap", () => { expect(enemy.getStatStage(s)).toBe(0); expect(player.getStatStage(s)).toBe(1); } - }, TIMEOUT); + }); }); diff --git a/src/test/moves/hyper_beam.test.ts b/src/test/moves/hyper_beam.test.ts index 7aa2dbfec2b..a6a471569ed 100644 --- a/src/test/moves/hyper_beam.test.ts +++ b/src/test/moves/hyper_beam.test.ts @@ -9,8 +9,6 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; // 20 sec timeout for all tests - describe("Moves - Hyper Beam", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -67,6 +65,6 @@ describe("Moves - Hyper Beam", () => { await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.hp).toBeLessThan(enemyPostAttackHp); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/jaw_lock.test.ts b/src/test/moves/jaw_lock.test.ts index 75fd6f0ff32..3398ec00b3b 100644 --- a/src/test/moves/jaw_lock.test.ts +++ b/src/test/moves/jaw_lock.test.ts @@ -11,7 +11,7 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Jaw Lock", () => { let phaserGame: Phaser.Game; @@ -61,7 +61,7 @@ describe("Moves - Jaw Lock", () => { expect(leadPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined(); expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined(); - }, TIMEOUT + } ); it( @@ -90,7 +90,7 @@ describe("Moves - Jaw Lock", () => { expect(leadPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); - }, TIMEOUT + } ); it( @@ -114,7 +114,7 @@ describe("Moves - Jaw Lock", () => { await game.doKillOpponents(); expect(leadPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); - }, TIMEOUT + } ); it( @@ -146,7 +146,7 @@ describe("Moves - Jaw Lock", () => { expect(enemyPokemon[1].getTag(BattlerTagType.TRAPPED)).toBeUndefined(); expect(playerPokemon[0].getTag(BattlerTagType.TRAPPED)).toBeDefined(); expect(playerPokemon[0].getTag(BattlerTagType.TRAPPED)?.sourceId).toBe(enemyPokemon[0].id); - }, TIMEOUT + } ); it( @@ -165,6 +165,6 @@ describe("Moves - Jaw Lock", () => { expect(playerPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/lash_out.test.ts b/src/test/moves/lash_out.test.ts index 8c414832f36..7a8ab6c5bb6 100644 --- a/src/test/moves/lash_out.test.ts +++ b/src/test/moves/lash_out.test.ts @@ -7,7 +7,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Lash Out", () => { let phaserGame: Phaser.Game; @@ -48,5 +48,5 @@ describe("Moves - Lash Out", () => { await game.phaseInterceptor.to("BerryPhase"); expect(allMoves[Moves.LASH_OUT].calculateBattlePower).toHaveReturnedWith(150); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/lucky_chant.test.ts b/src/test/moves/lucky_chant.test.ts index 57e5ff80f1d..77ea751aee1 100644 --- a/src/test/moves/lucky_chant.test.ts +++ b/src/test/moves/lucky_chant.test.ts @@ -7,7 +7,7 @@ import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import GameManager from "../utils/gameManager"; -const TIMEOUT = 20 * 1000; + describe("Moves - Lucky Chant", () => { let phaserGame: Phaser.Game; @@ -55,7 +55,7 @@ describe("Moves - Lucky Chant", () => { const secondTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp - firstTurnDamage; expect(secondTurnDamage).toBeLessThan(firstTurnDamage); - }, TIMEOUT + } ); it( @@ -81,7 +81,7 @@ describe("Moves - Lucky Chant", () => { const secondTurnDamage = playerPokemon[0].getMaxHp() - playerPokemon[0].hp - firstTurnDamage; expect(secondTurnDamage).toBeLessThan(firstTurnDamage); - }, TIMEOUT + } ); it( @@ -108,6 +108,6 @@ describe("Moves - Lucky Chant", () => { const secondTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp - firstTurnDamage; expect(secondTurnDamage).toBeLessThan(firstTurnDamage); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/make_it_rain.test.ts b/src/test/moves/make_it_rain.test.ts index 5ac35168f92..2b28a958ff0 100644 --- a/src/test/moves/make_it_rain.test.ts +++ b/src/test/moves/make_it_rain.test.ts @@ -8,7 +8,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; -const TIMEOUT = 20 * 1000; + describe("Moves - Make It Rain", () => { let phaserGame: Phaser.Game; @@ -46,7 +46,7 @@ describe("Moves - Make It Rain", () => { await game.phaseInterceptor.to(MoveEndPhase); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1); - }, TIMEOUT); + }); it("should apply effects even if the target faints", async () => { game.override.enemyLevel(1); // ensures the enemy will faint @@ -63,7 +63,7 @@ describe("Moves - Make It Rain", () => { expect(enemyPokemon.isFainted()).toBe(true); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1); - }, TIMEOUT); + }); it("should reduce Sp. Atk. once after KOing two enemies", async () => { game.override.enemyLevel(1); // ensures the enemy will faint @@ -80,7 +80,7 @@ describe("Moves - Make It Rain", () => { enemyPokemon.forEach(p => expect(p.isFainted()).toBe(true)); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1); - }, TIMEOUT); + }); it("should lower SPATK stat stage by 1 if it only hits the second target", async () => { await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); @@ -96,5 +96,5 @@ describe("Moves - Make It Rain", () => { await game.phaseInterceptor.to(MoveEndPhase); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/mat_block.test.ts b/src/test/moves/mat_block.test.ts index b759f49bf98..0746f9bcfa9 100644 --- a/src/test/moves/mat_block.test.ts +++ b/src/test/moves/mat_block.test.ts @@ -9,7 +9,7 @@ import { BerryPhase } from "#app/phases/berry-phase"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; -const TIMEOUT = 20 * 1000; + describe("Moves - Mat Block", () => { let phaserGame: Phaser.Game; @@ -56,7 +56,7 @@ describe("Moves - Mat Block", () => { await game.phaseInterceptor.to(BerryPhase, false); leadPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp())); - }, TIMEOUT + } ); test( @@ -77,7 +77,7 @@ describe("Moves - Mat Block", () => { await game.phaseInterceptor.to(BerryPhase, false); leadPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(-2)); - }, TIMEOUT + } ); test( @@ -103,6 +103,6 @@ describe("Moves - Mat Block", () => { await game.phaseInterceptor.to(BerryPhase, false); expect(leadPokemon.some((p, i) => p.hp < leadStartingHp[i])).toBeTruthy(); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/multi_target.test.ts b/src/test/moves/multi_target.test.ts index 5e830f23fc7..cd69482bd8e 100644 --- a/src/test/moves/multi_target.test.ts +++ b/src/test/moves/multi_target.test.ts @@ -7,7 +7,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Multi-target damage reduction", () => { let phaserGame: Phaser.Game; @@ -75,7 +75,7 @@ describe("Multi-target damage reduction", () => { // Moves that target all enemies get reduced if there's more than one enemy expect(gleam1).toBeLessThanOrEqual(Utils.toDmgValue(gleam2 * 0.75) + 1); expect(gleam1).toBeGreaterThanOrEqual(Utils.toDmgValue(gleam2 * 0.75) - 1); - }, TIMEOUT); + }); it("should reduce earthquake when more than one pokemon other than user is not fainted", async () => { await game.startBattle([Species.MAGIKARP, Species.FEEBAS]); @@ -126,5 +126,5 @@ describe("Multi-target damage reduction", () => { // Turn 3: 1 target, should be no damage reduction expect(damageEnemy1Turn1).toBeLessThanOrEqual(Utils.toDmgValue(damageEnemy1Turn3 * 0.75) + 1); expect(damageEnemy1Turn1).toBeGreaterThanOrEqual(Utils.toDmgValue(damageEnemy1Turn3 * 0.75) - 1); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/obstruct.test.ts b/src/test/moves/obstruct.test.ts index eb12daa785d..43706a5a1d6 100644 --- a/src/test/moves/obstruct.test.ts +++ b/src/test/moves/obstruct.test.ts @@ -8,8 +8,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Moves - Obstruct", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -41,7 +39,7 @@ describe("Moves - Obstruct", () => { expect(player.isFullHp()).toBe(true); expect(enemy.getStatStage(Stat.DEF)).toBe(-2); - }, TIMEOUT); + }); it("bypasses accuracy checks when applying protection and defense reduction", async () => { game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH)); @@ -57,7 +55,7 @@ describe("Moves - Obstruct", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(player.isFullHp()).toBe(true); expect(enemy.getStatStage(Stat.DEF)).toBe(-2); - }, TIMEOUT + } ); it("protects from non-contact damaging moves and doesn't lower the opponent's defense by 2 stages", async () => { @@ -72,7 +70,7 @@ describe("Moves - Obstruct", () => { expect(player.isFullHp()).toBe(true); expect(enemy.getStatStage(Stat.DEF)).toBe(0); - }, TIMEOUT); + }); it("doesn't protect from status moves", async () => { game.override.enemyMoveset(Array(4).fill(Moves.GROWL)); @@ -84,5 +82,5 @@ describe("Moves - Obstruct", () => { const player = game.scene.getPlayerPokemon()!; expect(player.getStatStage(Stat.ATK)).toBe(-1); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/octolock.test.ts b/src/test/moves/octolock.test.ts index 7618b08e9fc..d80b71a51e1 100644 --- a/src/test/moves/octolock.test.ts +++ b/src/test/moves/octolock.test.ts @@ -36,7 +36,7 @@ describe("Moves - Octolock", () => { .ability(Abilities.BALL_FETCH); }); - it("lowers DEF and SPDEF stat stages of the target Pokemon by 1 each turn", { timeout: 10000 }, async () => { + it("lowers DEF and SPDEF stat stages of the target Pokemon by 1 each turn", async () => { await game.classicMode.startBattle([ Species.GRAPPLOCT ]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -57,7 +57,7 @@ describe("Moves - Octolock", () => { expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-2); }); - it("if target pokemon has BIG_PECKS, should only lower SPDEF stat stage by 1", { timeout: 10000 }, async () => { + it("if target pokemon has BIG_PECKS, should only lower SPDEF stat stage by 1", async () => { game.override.enemyAbility(Abilities.BIG_PECKS); await game.classicMode.startBattle([ Species.GRAPPLOCT ]); @@ -71,7 +71,7 @@ describe("Moves - Octolock", () => { expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-1); }); - it("if target pokemon has WHITE_SMOKE, should not reduce any stat stages", { timeout: 10000 }, async () => { + it("if target pokemon has WHITE_SMOKE, should not reduce any stat stages", async () => { game.override.enemyAbility(Abilities.WHITE_SMOKE); await game.classicMode.startBattle([ Species.GRAPPLOCT ]); @@ -85,7 +85,7 @@ describe("Moves - Octolock", () => { expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(0); }); - it("if target pokemon has CLEAR_BODY, should not reduce any stat stages", { timeout: 10000 }, async () => { + it("if target pokemon has CLEAR_BODY, should not reduce any stat stages", async () => { game.override.enemyAbility(Abilities.CLEAR_BODY); await game.classicMode.startBattle([ Species.GRAPPLOCT ]); @@ -99,7 +99,7 @@ describe("Moves - Octolock", () => { expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(0); }); - it("traps the target pokemon", { timeout: 10000 }, async () => { + it("traps the target pokemon", async () => { await game.classicMode.startBattle([ Species.GRAPPLOCT ]); const enemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/src/test/moves/parting_shot.test.ts b/src/test/moves/parting_shot.test.ts index 52cfaf98111..fa328e15a32 100644 --- a/src/test/moves/parting_shot.test.ts +++ b/src/test/moves/parting_shot.test.ts @@ -10,7 +10,7 @@ import { FaintPhase } from "#app/phases/faint-phase"; import { MessagePhase } from "#app/phases/message-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; -const TIMEOUT = 20 * 1000; + describe("Moves - Parting Shot", () => { let phaserGame: Phaser.Game; @@ -53,7 +53,7 @@ describe("Moves - Parting Shot", () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW); - }, TIMEOUT + } ); test( @@ -73,7 +73,7 @@ describe("Moves - Parting Shot", () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW); - }, TIMEOUT + } ); it.skip( // TODO: fix this bug to pass the test! @@ -115,7 +115,7 @@ describe("Moves - Parting Shot", () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-6); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-6); expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW); - }, TIMEOUT + } ); it.skip( // TODO: fix this bug to pass the test! @@ -136,7 +136,7 @@ describe("Moves - Parting Shot", () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW); - }, TIMEOUT + } ); it.skip( // TODO: fix this bug to pass the test! @@ -156,7 +156,7 @@ describe("Moves - Parting Shot", () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW); - }, TIMEOUT + } ); it.skip( // TODO: fix this bug to pass the test! @@ -173,7 +173,7 @@ describe("Moves - Parting Shot", () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-1); expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW); - }, TIMEOUT + } ); it.skip( // TODO: fix this bug to pass the test! @@ -196,6 +196,6 @@ describe("Moves - Parting Shot", () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MEOWTH); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/power_shift.test.ts b/src/test/moves/power_shift.test.ts index 3fda315193e..f39759f278b 100644 --- a/src/test/moves/power_shift.test.ts +++ b/src/test/moves/power_shift.test.ts @@ -9,8 +9,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Moves - Power Shift", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -59,5 +57,5 @@ describe("Moves - Power Shift", () => { // Raw stats are swapped expect(playerPokemon.getStat(Stat.ATK, false)).toBe(20); expect(playerPokemon.getStat(Stat.DEF, false)).toBe(10); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/power_swap.test.ts b/src/test/moves/power_swap.test.ts index 92cd786c050..e9a4b569c92 100644 --- a/src/test/moves/power_swap.test.ts +++ b/src/test/moves/power_swap.test.ts @@ -11,8 +11,6 @@ import { MoveEndPhase } from "#app/phases/move-end-phase"; describe("Moves - Power Swap", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -65,5 +63,5 @@ describe("Moves - Power Swap", () => { expect(enemy.getStatStage(s)).toBe(1); } } - }, TIMEOUT); + }); }); diff --git a/src/test/moves/protect.test.ts b/src/test/moves/protect.test.ts index 24bbcbb9d34..dcf4211ac7f 100644 --- a/src/test/moves/protect.test.ts +++ b/src/test/moves/protect.test.ts @@ -10,7 +10,7 @@ import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { BattlerIndex } from "#app/battle"; import { MoveResult } from "#app/field/pokemon"; -const TIMEOUT = 20 * 1000; + describe("Moves - Protect", () => { let phaserGame: Phaser.Game; @@ -53,7 +53,7 @@ describe("Moves - Protect", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); - }, TIMEOUT + } ); test( @@ -72,7 +72,7 @@ describe("Moves - Protect", () => { expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeUndefined(); - }, TIMEOUT + } ); test( @@ -89,7 +89,7 @@ describe("Moves - Protect", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0); - }, TIMEOUT + } ); test( @@ -108,7 +108,7 @@ describe("Moves - Protect", () => { expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(enemyPokemon.turnData.hitCount).toBe(1); - }, TIMEOUT + } ); test( @@ -129,6 +129,6 @@ describe("Moves - Protect", () => { expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); expect(leadPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/purify.test.ts b/src/test/moves/purify.test.ts index 15d684b2d60..3ba9dfcbb65 100644 --- a/src/test/moves/purify.test.ts +++ b/src/test/moves/purify.test.ts @@ -8,7 +8,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Purify", () => { let phaserGame: Phaser.Game; @@ -55,7 +55,6 @@ describe("Moves - Purify", () => { expect(enemyPokemon.status).toBeNull(); expect(playerPokemon.isFullHp()).toBe(true); }, - TIMEOUT ); test( @@ -74,7 +73,6 @@ describe("Moves - Purify", () => { expect(playerPokemon.hp).toBe(playerInitialHp); }, - TIMEOUT ); }); diff --git a/src/test/moves/quick_guard.test.ts b/src/test/moves/quick_guard.test.ts index 9ab0fe1509c..e03beeac06a 100644 --- a/src/test/moves/quick_guard.test.ts +++ b/src/test/moves/quick_guard.test.ts @@ -8,7 +8,7 @@ import { Stat } from "#enums/stat"; import { BattlerIndex } from "#app/battle"; import { MoveResult } from "#app/field/pokemon"; -const TIMEOUT = 20 * 1000; + describe("Moves - Quick Guard", () => { let phaserGame: Phaser.Game; @@ -52,7 +52,7 @@ describe("Moves - Quick Guard", () => { await game.phaseInterceptor.to("BerryPhase", false); playerPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp())); - }, TIMEOUT + } ); test( @@ -71,7 +71,7 @@ describe("Moves - Quick Guard", () => { await game.phaseInterceptor.to("BerryPhase", false); playerPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0)); - }, TIMEOUT + } ); test( @@ -113,6 +113,6 @@ describe("Moves - Quick Guard", () => { expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/rage_powder.test.ts b/src/test/moves/rage_powder.test.ts index 86bc48ef882..bb31a1f2194 100644 --- a/src/test/moves/rage_powder.test.ts +++ b/src/test/moves/rage_powder.test.ts @@ -6,7 +6,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Rage Powder", () => { let phaserGame: Phaser.Game; @@ -50,7 +50,7 @@ describe("Moves - Rage Powder", () => { // If redirection was bypassed, both enemies should be damaged expect(enemyPokemon[0].hp).toBeLessThan(enemyPokemon[0].getMaxHp()); expect(enemyPokemon[1].hp).toBeLessThan(enemyPokemon[0].getMaxHp()); - }, TIMEOUT + } ); test( @@ -76,6 +76,6 @@ describe("Moves - Rage Powder", () => { // If redirection was bypassed, both enemies should be damaged expect(enemyPokemon[0].hp).toBeLessThan(enemyStartingHp[0]); expect(enemyPokemon[1].hp).toBeLessThan(enemyStartingHp[1]); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/relic_song.test.ts b/src/test/moves/relic_song.test.ts index 373d88f0434..67fc557a318 100644 --- a/src/test/moves/relic_song.test.ts +++ b/src/test/moves/relic_song.test.ts @@ -10,8 +10,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Moves - Relic Song", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -47,7 +45,7 @@ describe("Moves - Relic Song", () => { await game.phaseInterceptor.to("BerryPhase"); expect(meloetta.formIndex).toBe(0); - }, TIMEOUT); + }); it("doesn't swap Meloetta's form during a mono-type challenge", async () => { game.challengeMode.addChallenge(Challenges.SINGLE_TYPE, Type.PSYCHIC + 1, 0); @@ -62,7 +60,7 @@ describe("Moves - Relic Song", () => { await game.toNextTurn(); expect(meloetta.formIndex).toBe(0); - }, TIMEOUT); + }); it("doesn't swap Meloetta's form during biome change (arena reset)", async () => { game.override @@ -77,5 +75,5 @@ describe("Moves - Relic Song", () => { await game.toNextWave(); expect(meloetta.formIndex).toBe(1); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/roost.test.ts b/src/test/moves/roost.test.ts index c1fc962e876..a1c473c0632 100644 --- a/src/test/moves/roost.test.ts +++ b/src/test/moves/roost.test.ts @@ -9,7 +9,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Roost", () => { let phaserGame: Phaser.Game; @@ -72,7 +72,7 @@ describe("Moves - Roost", () => { expect(playerPokemonTypes[0] === Type.NORMAL).toBeTruthy(); expect(playerPokemonTypes.length === 1).toBeTruthy(); expect(playerPokemon.isGrounded()).toBeTruthy(); - }, TIMEOUT + } ); test( @@ -100,7 +100,7 @@ describe("Moves - Roost", () => { expect(playerPokemonTypes[0] === Type.FLYING).toBeTruthy(); expect(playerPokemon.isGrounded()).toBeFalsy(); - }, TIMEOUT + } ); test( @@ -128,7 +128,7 @@ describe("Moves - Roost", () => { expect(playerPokemonTypes[1] === Type.FLYING).toBeTruthy(); expect(playerPokemon.isGrounded()).toBeFalsy(); - }, TIMEOUT + } ); test( @@ -157,7 +157,7 @@ describe("Moves - Roost", () => { expect(playerPokemonTypes[1] === Type.FLYING).toBeTruthy(); expect(playerPokemon.isGrounded()).toBeFalsy(); - }, TIMEOUT + } ); test( @@ -196,7 +196,7 @@ describe("Moves - Roost", () => { expect(playerPokemonTypes.length === 1).toBeTruthy(); expect(playerPokemon.isGrounded()).toBeFalsy(); - }, TIMEOUT + } ); test( @@ -236,7 +236,7 @@ describe("Moves - Roost", () => { expect(playerPokemonTypes.length === 1).toBeTruthy(); expect(playerPokemon.isGrounded()).toBeFalsy(); - }, TIMEOUT + } ); test( @@ -263,7 +263,7 @@ describe("Moves - Roost", () => { expect(playerPokemonTypes.length === 3).toBeTruthy(); expect(playerPokemon.isGrounded()).toBeFalsy(); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/safeguard.test.ts b/src/test/moves/safeguard.test.ts index 2caf698a73a..b21698d0298 100644 --- a/src/test/moves/safeguard.test.ts +++ b/src/test/moves/safeguard.test.ts @@ -8,7 +8,7 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Safeguard", () => { let phaserGame: Phaser.Game; @@ -46,7 +46,7 @@ describe("Moves - Safeguard", () => { await game.toNextTurn(); expect(enemy.status).toBeUndefined(); - }, TIMEOUT); + }); it("protects from status moves", async () => { await game.classicMode.startBattle(); @@ -57,7 +57,7 @@ describe("Moves - Safeguard", () => { await game.toNextTurn(); expect(enemyPokemon.status).toBeUndefined(); - }, TIMEOUT); + }); it("protects from confusion", async () => { game.override.moveset([Moves.CONFUSE_RAY]); @@ -69,7 +69,7 @@ describe("Moves - Safeguard", () => { await game.toNextTurn(); expect(enemyPokemon.summonData.tags).toEqual([]); - }, TIMEOUT); + }); it("protects ally from status", async () => { game.override.battleType("double"); @@ -87,7 +87,7 @@ describe("Moves - Safeguard", () => { expect(enemyPokemon[0].status).toBeUndefined(); expect(enemyPokemon[1].status).toBeUndefined(); - }, TIMEOUT); + }); it("protects from Yawn", async () => { await game.classicMode.startBattle(); @@ -98,7 +98,7 @@ describe("Moves - Safeguard", () => { await game.toNextTurn(); expect(enemyPokemon.summonData.tags).toEqual([]); - }, TIMEOUT); + }); it("doesn't protect from already existing Yawn", async () => { await game.classicMode.startBattle(); @@ -112,7 +112,7 @@ describe("Moves - Safeguard", () => { await game.toNextTurn(); expect(enemyPokemon.status?.effect).toEqual(StatusEffect.SLEEP); - }, TIMEOUT); + }); it("doesn't protect from self-inflicted via Rest or Flame Orb", async () => { game.override.enemyHeldItems([{name: "FLAME_ORB"}]); @@ -135,7 +135,7 @@ describe("Moves - Safeguard", () => { await game.toNextTurn(); expect(enemyPokemon.status?.effect).toEqual(StatusEffect.SLEEP); - }, TIMEOUT); + }); it("protects from ability-inflicted status", async () => { game.override.ability(Abilities.STATIC); @@ -151,5 +151,5 @@ describe("Moves - Safeguard", () => { await game.toNextTurn(); expect(enemyPokemon.status).toBeUndefined(); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/shell_side_arm.test.ts b/src/test/moves/shell_side_arm.test.ts new file mode 100644 index 00000000000..ded7ed82fd1 --- /dev/null +++ b/src/test/moves/shell_side_arm.test.ts @@ -0,0 +1,85 @@ +import { BattlerIndex } from "#app/battle"; +import { allMoves, ShellSideArmCategoryAttr } from "#app/data/move"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; + +describe("Moves - Shell Side Arm", () => { + 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.SHELL_SIDE_ARM]) + .battleType("single") + .startingLevel(100) + .enemyLevel(100) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("becomes a physical attack if forecasted to deal more damage as physical", async () => { + game.override.enemySpecies(Species.SNORLAX); + + await game.classicMode.startBattle([Species.MANAPHY]); + + const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM]; + const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0]; + vi.spyOn(shellSideArmAttr, "apply"); + + game.move.select(Moves.SHELL_SIDE_ARM); + + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(shellSideArmAttr.apply).toHaveLastReturnedWith(true); + }); + + it("remains a special attack if forecasted to deal more damage as special", async () => { + game.override.enemySpecies(Species.SLOWBRO); + + await game.classicMode.startBattle([Species.MANAPHY]); + + const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM]; + const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0]; + vi.spyOn(shellSideArmAttr, "apply"); + + game.move.select(Moves.SHELL_SIDE_ARM); + + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false); + }); + + it("respects stat stage changes when forecasting base damage", async () => { + game.override + .enemySpecies(Species.SNORLAX) + .enemyMoveset(Moves.COTTON_GUARD); + + await game.classicMode.startBattle([Species.MANAPHY]); + + const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM]; + const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0]; + vi.spyOn(shellSideArmAttr, "apply"); + + game.move.select(Moves.SHELL_SIDE_ARM); + + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + + await game.phaseInterceptor.to("BerryPhase", false); + + expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false); + }); +}); diff --git a/src/test/moves/shell_trap.test.ts b/src/test/moves/shell_trap.test.ts index 213b9c3fd0a..1dae00e24a5 100644 --- a/src/test/moves/shell_trap.test.ts +++ b/src/test/moves/shell_trap.test.ts @@ -10,7 +10,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Shell Trap", () => { let phaserGame: Phaser.Game; @@ -60,7 +60,7 @@ describe("Moves - Shell Trap", () => { await game.phaseInterceptor.to(MoveEndPhase); enemyPokemon.forEach(p => expect(p.hp).toBeLessThan(p.getMaxHp())); - }, TIMEOUT + } ); it( @@ -86,7 +86,7 @@ describe("Moves - Shell Trap", () => { await game.phaseInterceptor.to(BerryPhase, false); enemyPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp())); - }, TIMEOUT + } ); it( @@ -112,7 +112,7 @@ describe("Moves - Shell Trap", () => { await game.phaseInterceptor.to(BerryPhase, false); enemyPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp())); - }, TIMEOUT + } ); it( @@ -138,7 +138,7 @@ describe("Moves - Shell Trap", () => { await game.phaseInterceptor.to(BerryPhase, false); enemyPokemon.forEach((p, i) => expect(p.hp).toBe(enemyStartingHp[i])); - }, TIMEOUT + } ); it( @@ -158,6 +158,6 @@ describe("Moves - Shell Trap", () => { expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/spit_up.test.ts b/src/test/moves/spit_up.test.ts index acf7f01d991..412360c2664 100644 --- a/src/test/moves/spit_up.test.ts +++ b/src/test/moves/spit_up.test.ts @@ -43,7 +43,7 @@ describe("Moves - Spit Up", () => { }); describe("consumes all stockpile stacks to deal damage (scaling with stacks)", () => { - it("1 stack -> 100 power", { timeout: 10000 }, async () => { + it("1 stack -> 100 power", async () => { const stacksToSetup = 1; const expectedPower = 100; @@ -65,7 +65,7 @@ describe("Moves - Spit Up", () => { expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); }); - it("2 stacks -> 200 power", { timeout: 10000 }, async () => { + it("2 stacks -> 200 power", async () => { const stacksToSetup = 2; const expectedPower = 200; @@ -88,7 +88,7 @@ describe("Moves - Spit Up", () => { expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); }); - it("3 stacks -> 300 power", { timeout: 10000 }, async () => { + it("3 stacks -> 300 power", async () => { const stacksToSetup = 3; const expectedPower = 300; @@ -113,7 +113,7 @@ describe("Moves - Spit Up", () => { }); }); - it("fails without stacks", { timeout: 10000 }, async () => { + it("fails without stacks", async () => { await game.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; @@ -130,7 +130,7 @@ describe("Moves - Spit Up", () => { }); describe("restores stat boosts granted by stacks", () => { - it("decreases stats based on stored values (both boosts equal)", { timeout: 10000 }, async () => { + it("decreases stats based on stored values (both boosts equal)", async () => { await game.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; @@ -157,7 +157,7 @@ describe("Moves - Spit Up", () => { expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); }); - it("decreases stats based on stored values (different boosts)", { timeout: 10000 }, async () => { + it("decreases stats based on stored values (different boosts)", async () => { await game.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; diff --git a/src/test/moves/spotlight.test.ts b/src/test/moves/spotlight.test.ts index aef44369642..6324c3dc6ec 100644 --- a/src/test/moves/spotlight.test.ts +++ b/src/test/moves/spotlight.test.ts @@ -6,7 +6,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Spotlight", () => { let phaserGame: Phaser.Game; @@ -50,7 +50,7 @@ describe("Moves - Spotlight", () => { expect(enemyPokemon[0].hp).toBeLessThan(enemyPokemon[0].getMaxHp()); expect(enemyPokemon[1].hp).toBe(enemyPokemon[1].getMaxHp()); - }, TIMEOUT + } ); test( @@ -70,6 +70,6 @@ describe("Moves - Spotlight", () => { expect(enemyPokemon[0].hp).toBeLessThan(enemyPokemon[0].getMaxHp()); expect(enemyPokemon[1].hp).toBe(enemyPokemon[1].getMaxHp()); - }, TIMEOUT + } ); }); diff --git a/src/test/moves/stockpile.test.ts b/src/test/moves/stockpile.test.ts index 8e7a44d053b..141ce79eb33 100644 --- a/src/test/moves/stockpile.test.ts +++ b/src/test/moves/stockpile.test.ts @@ -37,7 +37,7 @@ describe("Moves - Stockpile", () => { game.override.ability(Abilities.NONE); }); - it("gains a stockpile stack and raises user's DEF and SPDEF stat stages by 1 on each use, fails at max stacks (3)", { timeout: 10000 }, async () => { + it("gains a stockpile stack and raises user's DEF and SPDEF stat stages by 1 on each use, fails at max stacks (3)", async () => { await game.startBattle([Species.ABOMASNOW]); const user = game.scene.getPlayerPokemon()!; @@ -76,7 +76,7 @@ describe("Moves - Stockpile", () => { } }); - it("gains a stockpile stack even if user's DEF and SPDEF stat stages are at +6", { timeout: 10000 }, async () => { + it("gains a stockpile stack even if user's DEF and SPDEF stat stages are at +6", async () => { await game.startBattle([Species.ABOMASNOW]); const user = game.scene.getPlayerPokemon()!; diff --git a/src/test/moves/substitute.test.ts b/src/test/moves/substitute.test.ts index 3976247d489..6c18579e7f6 100644 --- a/src/test/moves/substitute.test.ts +++ b/src/test/moves/substitute.test.ts @@ -16,8 +16,6 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; // 20 sec timeout - describe("Moves - Substitute", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -57,7 +55,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("MoveEndPhase", false); expect(leadPokemon.hp).toBe(Math.ceil(leadPokemon.getMaxHp() * 3/4)); - }, TIMEOUT + } ); it( @@ -81,7 +79,7 @@ describe("Moves - Substitute", () => { expect(leadPokemon.hp).toBe(postSubHp); expect(leadPokemon.getTag(BattlerTagType.SUBSTITUTE)).toBeDefined(); - }, TIMEOUT + } ); it( @@ -107,7 +105,7 @@ describe("Moves - Substitute", () => { expect(leadPokemon.hp).toBe(postSubHp); expect(leadPokemon.getTag(BattlerTagType.SUBSTITUTE)).toBeUndefined(); - }, TIMEOUT + } ); it( @@ -148,7 +146,7 @@ describe("Moves - Substitute", () => { expect(leadPokemon.getTag(BattlerTagType.SUBSTITUTE)).toBeDefined(); expect(leadPokemon.hp).toBeLessThan(postSubHp); - }, TIMEOUT + } ); it( @@ -172,7 +170,7 @@ describe("Moves - Substitute", () => { expect(leadPokemon.getTag(BattlerTagType.SUBSTITUTE)).toBeDefined(); expect(leadPokemon.hp).toBeLessThan(postSubHp); - }, TIMEOUT + } ); it( @@ -192,7 +190,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("MoveEndPhase", false); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2); - }, TIMEOUT + } ); it( @@ -213,7 +211,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); it( @@ -233,7 +231,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(leadPokemon.getTag(TrappedTag)).toBeUndefined(); - }, TIMEOUT + } ); it( @@ -253,7 +251,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(leadPokemon.getStatStage(Stat.DEF)).toBe(0); - }, TIMEOUT + } ); it( @@ -272,7 +270,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(leadPokemon.status?.effect).not.toBe(StatusEffect.PARALYSIS); - }, TIMEOUT + } ); it( @@ -293,7 +291,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(leadPokemon.getHeldItems().length).toBe(1); - }, TIMEOUT + } ); it( @@ -314,7 +312,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("MoveEndPhase", false); expect(enemyPokemon.getHeldItems().length).toBe(enemyNumItems); - }, TIMEOUT + } ); it( @@ -339,7 +337,7 @@ describe("Moves - Substitute", () => { expect(leadPokemon.getHeldItems().length).toBe(1); expect(enemyPokemon.hp).toBe(enemyPostAttackHp); - }, TIMEOUT + } ); it( @@ -358,7 +356,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2); - }, TIMEOUT + } ); it( @@ -404,7 +402,7 @@ describe("Moves - Substitute", () => { const subTag = switchedPokemon.getTag(SubstituteTag)!; expect(subTag).toBeDefined(); expect(subTag.hp).toBe(Math.floor(leadPokemon.getMaxHp() * 1/4)); - }, TIMEOUT + } ); it( @@ -422,7 +420,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); it( @@ -447,7 +445,7 @@ describe("Moves - Substitute", () => { expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); it( @@ -467,7 +465,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); - }, TIMEOUT + } ); it( @@ -488,7 +486,7 @@ describe("Moves - Substitute", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemyPokemon.status?.effect).not.toBe(StatusEffect.BURN); - }, TIMEOUT + } ); it( diff --git a/src/test/moves/swallow.test.ts b/src/test/moves/swallow.test.ts index 5a0e63e6e78..b8ca941d0ee 100644 --- a/src/test/moves/swallow.test.ts +++ b/src/test/moves/swallow.test.ts @@ -38,7 +38,7 @@ describe("Moves - Swallow", () => { }); describe("consumes all stockpile stacks to heal (scaling with stacks)", () => { - it("1 stack -> 25% heal", { timeout: 10000 }, async () => { + it("1 stack -> 25% heal", async () => { const stacksToSetup = 1; const expectedHeal = 25; @@ -65,7 +65,7 @@ describe("Moves - Swallow", () => { expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); }); - it("2 stacks -> 50% heal", { timeout: 10000 }, async () => { + it("2 stacks -> 50% heal", async () => { const stacksToSetup = 2; const expectedHeal = 50; @@ -93,7 +93,7 @@ describe("Moves - Swallow", () => { expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); }); - it("3 stacks -> 100% heal", { timeout: 10000 }, async () => { + it("3 stacks -> 100% heal", async () => { const stacksToSetup = 3; const expectedHeal = 100; @@ -123,7 +123,7 @@ describe("Moves - Swallow", () => { }); }); - it("fails without stacks", { timeout: 10000 }, async () => { + it("fails without stacks", async () => { await game.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; @@ -138,7 +138,7 @@ describe("Moves - Swallow", () => { }); describe("restores stat stage boosts granted by stacks", () => { - it("decreases stats based on stored values (both boosts equal)", { timeout: 10000 }, async () => { + it("decreases stats based on stored values (both boosts equal)", async () => { await game.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; @@ -163,7 +163,7 @@ describe("Moves - Swallow", () => { expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); }); - it("lower stat stages based on stored values (different boosts)", { timeout: 10000 }, async () => { + it("lower stat stages based on stored values (different boosts)", async () => { await game.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; diff --git a/src/test/moves/tar_shot.test.ts b/src/test/moves/tar_shot.test.ts index 2963f061fc6..2385bd18265 100644 --- a/src/test/moves/tar_shot.test.ts +++ b/src/test/moves/tar_shot.test.ts @@ -11,8 +11,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite describe("Moves - Tar Shot", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -54,7 +52,7 @@ describe("Moves - Tar Shot", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.getMoveEffectiveness).toHaveReturnedWith(4); - }, TIMEOUT); + }); it("will not double the effectiveness of Fire-type moves used on a target that is already under the effect of Tar Shot (but may still lower its Speed)", async () => { await game.classicMode.startBattle([Species.PIKACHU]); @@ -82,7 +80,7 @@ describe("Moves - Tar Shot", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.getMoveEffectiveness).toHaveReturnedWith(4); - }, TIMEOUT); + }); it("does not double the effectiveness of Fire-type moves against a Pokémon that is Terastallized", async () => { game.override.enemyHeldItems([{ name: "TERA_SHARD", type: Type.GRASS }]).enemySpecies(Species.SPRIGATITO); @@ -104,7 +102,7 @@ describe("Moves - Tar Shot", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); - }, TIMEOUT); + }); it("doubles the effectiveness of Fire-type moves against a Pokémon that is already under the effects of Tar Shot before it Terastallized", async () => { game.override.enemySpecies(Species.SPRIGATITO); @@ -128,5 +126,5 @@ describe("Moves - Tar Shot", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.getMoveEffectiveness).toHaveReturnedWith(4); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/thousand_arrows.test.ts b/src/test/moves/thousand_arrows.test.ts index 8d1d6ee5f4a..ad9281dc45e 100644 --- a/src/test/moves/thousand_arrows.test.ts +++ b/src/test/moves/thousand_arrows.test.ts @@ -8,7 +8,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Thousand Arrows", () => { let phaserGame: Phaser.Game; @@ -51,7 +51,7 @@ describe("Moves - Thousand Arrows", () => { expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); it( @@ -74,7 +74,7 @@ describe("Moves - Thousand Arrows", () => { expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); - }, TIMEOUT + } ); it( diff --git a/src/test/moves/throat_chop.test.ts b/src/test/moves/throat_chop.test.ts index cb34b4bafff..2a0ab675b25 100644 --- a/src/test/moves/throat_chop.test.ts +++ b/src/test/moves/throat_chop.test.ts @@ -10,8 +10,6 @@ import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest"; describe("Moves - Throat Chop", () => { let phaserGame: Phaser.Game; let game: GameManager; - const TIMEOUT = 20 * 1000; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -53,5 +51,5 @@ describe("Moves - Throat Chop", () => { await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.isFullHp()).toBe(false); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/thunder_wave.test.ts b/src/test/moves/thunder_wave.test.ts index 7ad59518013..28c5da4717b 100644 --- a/src/test/moves/thunder_wave.test.ts +++ b/src/test/moves/thunder_wave.test.ts @@ -7,7 +7,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; + describe("Moves - Thunder Wave", () => { let phaserGame: Phaser.Game; @@ -45,7 +45,7 @@ describe("Moves - Thunder Wave", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.status?.effect).toBe(StatusEffect.PARALYSIS); - }, TIMEOUT); + }); it("does not paralyze if the Pokemon is a Ground-type", async () => { game.override.enemySpecies(Species.DIGLETT); @@ -58,7 +58,7 @@ describe("Moves - Thunder Wave", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.status).toBeUndefined(); - }, TIMEOUT); + }); it("does not paralyze if the Pokemon already has a status effect", async () => { game.override.enemySpecies(Species.MAGIKARP).enemyStatusEffect(StatusEffect.BURN); @@ -71,7 +71,7 @@ describe("Moves - Thunder Wave", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.status?.effect).not.toBe(StatusEffect.PARALYSIS); - }, TIMEOUT); + }); it("affects Ground types if the user has Normalize", async () => { game.override.ability(Abilities.NORMALIZE).enemySpecies(Species.DIGLETT); @@ -84,7 +84,7 @@ describe("Moves - Thunder Wave", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.status?.effect).toBe(StatusEffect.PARALYSIS); - }, TIMEOUT); + }); it("does not affect Ghost types if the user has Normalize", async () => { game.override.ability(Abilities.NORMALIZE).enemySpecies(Species.HAUNTER); @@ -97,5 +97,5 @@ describe("Moves - Thunder Wave", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.status).toBeUndefined(); - }, TIMEOUT); + }); }); diff --git a/src/test/moves/wide_guard.test.ts b/src/test/moves/wide_guard.test.ts index b4e6e305539..9ddd8905ff6 100644 --- a/src/test/moves/wide_guard.test.ts +++ b/src/test/moves/wide_guard.test.ts @@ -8,7 +8,7 @@ import { Stat } from "#enums/stat"; import { BerryPhase } from "#app/phases/berry-phase"; import { CommandPhase } from "#app/phases/command-phase"; -const TIMEOUT = 20 * 1000; + describe("Moves - Wide Guard", () => { let phaserGame: Phaser.Game; @@ -55,7 +55,7 @@ describe("Moves - Wide Guard", () => { await game.phaseInterceptor.to(BerryPhase, false); leadPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp())); - }, TIMEOUT + } ); test( @@ -76,7 +76,7 @@ describe("Moves - Wide Guard", () => { await game.phaseInterceptor.to(BerryPhase, false); leadPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0)); - }, TIMEOUT + } ); test( @@ -97,7 +97,7 @@ describe("Moves - Wide Guard", () => { await game.phaseInterceptor.to(BerryPhase, false); expect(leadPokemon.some(p => p.hp < p.getMaxHp())).toBeTruthy(); - }, TIMEOUT + } ); test( @@ -120,6 +120,6 @@ describe("Moves - Wide Guard", () => { expect(leadPokemon[0].hp).toBe(leadPokemon[0].getMaxHp()); enemyPokemon.forEach(p => expect(p.hp).toBeLessThan(p.getMaxHp())); - }, TIMEOUT + } ); }); diff --git a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index b4cc186864c..3dc90427eb2 100644 --- a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -69,22 +69,6 @@ describe("A Trainer's Test - Mystery Encounter", () => { expect(ATrainersTestEncounter.options.length).toBe(2); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.A_TRAINERS_TEST); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully ", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = ATrainersTestEncounter; diff --git a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index 7cca7abba27..58c8e1fbc30 100644 --- a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -66,22 +66,6 @@ describe("Absolute Avarice - Mystery Encounter", () => { expect(AbsoluteAvariceEncounter.options.length).toBe(3); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.ABSOLUTE_AVARICE); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should not spawn outside of proper biomes", async () => { game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); game.override.startingBiome(Biome.VOLCANO); diff --git a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 1c68852a63d..c39e636b462 100644 --- a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -80,22 +80,6 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully ", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = AnOfferYouCantRefuseEncounter; @@ -247,7 +231,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { }); describe("Option 3 - Leave", () => { - it("should leave encounter without battle", async () => { + it.each(Array.from({length: 30}))("should leave encounter without battle", async () => { const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty); diff --git a/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index 73ffad36258..78f4a477216 100644 --- a/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -19,7 +19,7 @@ import { CommandPhase } from "#app/phases/command-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; const namespace = "mysteryEncounter:berriesAbound"; -const defaultParty = [Species.PYUKUMUKU]; +const defaultParty = [Species.PYUKUMUKU, Species.MAGIKARP, Species.PIKACHU]; const defaultBiome = Biome.CAVE; const defaultWave = 45; @@ -69,22 +69,6 @@ describe("Berries Abound - Mystery Encounter", () => { expect(BerriesAboundEncounter.options.length).toBe(3); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.BERRIES_ABOUND); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = BerriesAboundEncounter; @@ -98,7 +82,6 @@ describe("Berries Abound - Mystery Encounter", () => { const config = BerriesAboundEncounter.enemyPartyConfigs[0]; expect(config).toBeDefined(); - expect(config.levelAdditiveMultiplier).toBe(1); expect(config.pokemonConfigs?.[0].isBoss).toBe(true); expect(onInitResult).toBe(true); }); @@ -133,8 +116,10 @@ describe("Berries Abound - Mystery Encounter", () => { expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); }); - // TODO: there is some severe test flakiness occurring for this file, needs to be looked at/addressed in separate issue - it.skip("should reward the player with X berries based on wave", async () => { + /** + * Related issue-comment: {@link https://github.com/pagefaultgames/pokerogue/issues/4300#issuecomment-2362849444} + */ + it("should reward the player with X berries based on wave", async () => { await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty); const numBerries = game.scene.currentBattle.mysteryEncounter!.misc.numBerries; @@ -205,15 +190,14 @@ describe("Berries Abound - Mystery Encounter", () => { }); it("Should skip battle when fastest pokemon is faster than boss", async () => { - const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); - const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText"); + vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); + vi.spyOn(EncounterDialogueUtils, "showEncounterText"); await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty); - // Setting party pokemon's level arbitrarily high to outspeed - const fastestPokemon = scene.getParty()[0]; - fastestPokemon.level = 1000; - fastestPokemon.calculateStats(); + scene.getParty().forEach(pkm => { + vi.spyOn(pkm, "getStat").mockReturnValue(9999); // for ease return for every stat + }); await runMysteryEncounterToEnd(game, 2); await game.phaseInterceptor.to(SelectModifierPhase, false); @@ -227,8 +211,8 @@ describe("Berries Abound - Mystery Encounter", () => { expect(option.modifierTypeOption.type.id).toContain("BERRY"); } - expect(encounterTextSpy).toHaveBeenCalledWith(expect.any(BattleScene), `${namespace}.option.2.selected`); - expect(leaveEncounterWithoutBattleSpy).toBeCalled(); + expect(EncounterDialogueUtils.showEncounterText).toHaveBeenCalledWith(expect.any(BattleScene), `${namespace}.option.2.selected`); + expect(EncounterPhaseUtils.leaveEncounterWithoutBattle).toBeCalled(); }); }); diff --git a/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 70adf93d502..247acc9e5b6 100644 --- a/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -201,22 +201,6 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(BugTypeSuperfanEncounter.options.length).toBe(3); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.BUG_TYPE_SUPERFAN); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = BugTypeSuperfanEncounter; 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 383e3bd3564..5ed5a9487de 100644 --- a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -95,14 +95,6 @@ describe("Clowning Around - Mystery Encounter", () => { expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.CLOWNING_AROUND); }); - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = ClowningAroundEncounter; diff --git a/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index 5a2512ddaf6..cbf8251f2e7 100644 --- a/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -69,22 +69,6 @@ describe("Dancing Lessons - Mystery Encounter", () => { expect(DancingLessonsEncounter.options.length).toBe(3); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DANCING_LESSONS); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should not spawn outside of proper biomes", async () => { game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); game.override.startingBiome(Biome.SPACE); diff --git a/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index 969188dca06..7e452fd90c7 100644 --- a/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -66,22 +66,6 @@ describe("Delibird-y - Mystery Encounter", () => { expect(DelibirdyEncounter.options.length).toBe(3); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DELIBIRDY); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should not spawn if player does not have enough money", async () => { scene.money = 0; diff --git a/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts b/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts index f22bd832964..0b2d66db20b 100644 --- a/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts @@ -79,22 +79,6 @@ describe("Department Store Sale - Mystery Encounter", () => { expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DEPARTMENT_STORE_SALE); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DEPARTMENT_STORE_SALE); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - describe("Option 1 - TM Shop", () => { it("should have the correct properties", () => { const option = DepartmentStoreSaleEncounter.options[0]; diff --git a/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts b/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts index 7a8d951c5da..13550abb97c 100644 --- a/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts @@ -72,22 +72,6 @@ describe("Field Trip - Mystery Encounter", () => { expect(FieldTripEncounter.options.length).toBe(3); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FIELD_TRIP); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - describe("Option 1 - Show off a physical move", () => { it("should have the correct properties", () => { const option = FieldTripEncounter.options[0]; diff --git a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 445ab4491a4..cd11aa2628b 100644 --- a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -88,14 +88,6 @@ describe("Fiery Fallout - Mystery Encounter", () => { expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FIERY_FALLOUT); }); - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully ", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = FieryFalloutEncounter; diff --git a/src/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/src/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index 735dcc709bf..df2f32231ba 100644 --- a/src/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -67,22 +67,6 @@ describe("Fight or Flight - Mystery Encounter", () => { expect(FightOrFlightEncounter.options.length).toBe(3); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FIGHT_OR_FLIGHT); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = FightOrFlightEncounter; @@ -96,7 +80,6 @@ describe("Fight or Flight - Mystery Encounter", () => { const config = FightOrFlightEncounter.enemyPartyConfigs[0]; expect(config).toBeDefined(); - expect(config.levelAdditiveMultiplier).toBe(1); expect(config.pokemonConfigs?.[0].isBoss).toBe(true); expect(onInitResult).toBe(true); }); diff --git a/src/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts b/src/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts index 70250350af4..c337556728b 100644 --- a/src/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts @@ -85,22 +85,6 @@ describe("Fun And Games! - Mystery Encounter", () => { expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FUN_AND_GAMES); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FUN_AND_GAMES); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = new MysteryEncounter(FunAndGamesEncounter); diff --git a/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index e91b936cb9d..5a99b0450ca 100644 --- a/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -69,22 +69,6 @@ describe("Global Trade System - Mystery Encounter", () => { expect(GlobalTradeSystemEncounter.options.length).toBe(4); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.GLOBAL_TRADE_SYSTEM); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should not spawn outside of CIVILIZATION_ENCOUNTER_BIOMES", async () => { game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); game.override.startingBiome(Biome.VOLCANO); diff --git a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts index 82670e32daa..02872334fac 100644 --- a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts @@ -74,22 +74,6 @@ describe("Lost at Sea - Mystery Encounter", () => { expect(game.scene.currentBattle.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.LOST_AT_SEA); }); - it("should not run below wave 11", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(game.scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(game.scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully", () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = LostAtSeaEncounter; diff --git a/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index de527538711..15cd3338fff 100644 --- a/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -79,22 +79,6 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.MYSTERIOUS_CHALLENGERS); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.MYSTERIOUS_CHALLENGERS); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = new MysteryEncounter(MysteriousChallengersEncounter); @@ -117,12 +101,12 @@ describe("Mysterious Challengers - Mystery Encounter", () => { }, { trainerConfig: expect.any(TrainerConfig), - levelAdditiveMultiplier: 1, + levelAdditiveModifier: 1, female: expect.any(Boolean), }, { trainerConfig: expect.any(TrainerConfig), - levelAdditiveMultiplier: 1.5, + levelAdditiveModifier: 1.5, female: expect.any(Boolean), } ]); diff --git a/src/test/mystery-encounter/encounters/part-timer-encounter.test.ts b/src/test/mystery-encounter/encounters/part-timer-encounter.test.ts index f73c1f437d0..061b6a61461 100644 --- a/src/test/mystery-encounter/encounters/part-timer-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/part-timer-encounter.test.ts @@ -80,22 +80,6 @@ describe("Part-Timer - Mystery Encounter", () => { expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.PART_TIMER); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.PART_TIMER); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - describe("Option 1 - Make Deliveries", () => { it("should have the correct properties", () => { const option = PartTimerEncounter.options[0]; diff --git a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 13860e83baa..b02d00c7dbd 100644 --- a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -15,12 +15,15 @@ import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encoun import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import { Abilities } from "#app/enums/abilities"; const namespace = "mysteryEncounter:teleportingHijinks"; const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA]; const defaultBiome = Biome.CAVE; const defaultWave = 45; +const TRANSPORT_BIOMES = [Biome.SPACE, Biome.ISLAND, Biome.LABORATORY, Biome.FAIRY_CAVE, Biome.WASTELAND, Biome.DOJO]; + describe("Teleporting Hijinks - Mystery Encounter", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -34,10 +37,12 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { game = new GameManager(phaserGame); scene = game.scene; scene.money = 20000; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves() + .enemyPassiveAbility(Abilities.BALL_FETCH); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([ @@ -65,22 +70,6 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { expect(TeleportingHijinksEncounter.options.length).toBe(3); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.TELEPORTING_HIJINKS); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should run in waves that are X1", async () => { game.override.startingWave(11); game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); @@ -183,7 +172,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); expect(previousBiome).not.toBe(scene.arena.biomeType); - expect([Biome.SPACE, Biome.ISLAND, Biome.LABORATORY, Biome.FAIRY_CAVE]).toContain(scene.arena.biomeType); + expect(TRANSPORT_BIOMES).toContain(scene.arena.biomeType); }); it("should start a battle against an enraged boss", { retry: 5 }, async () => { @@ -246,7 +235,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, undefined, true); expect(previousBiome).not.toBe(scene.arena.biomeType); - expect([Biome.SPACE, Biome.ISLAND, Biome.LABORATORY, Biome.FAIRY_CAVE]).toContain(scene.arena.biomeType); + expect(TRANSPORT_BIOMES).toContain(scene.arena.biomeType); }); it("should start a battle against an enraged boss", async () => { diff --git a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts new file mode 100644 index 00000000000..59765148ead --- /dev/null +++ b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -0,0 +1,283 @@ +import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; +import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; +import { Biome } from "#app/enums/biome"; +import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; +import { Species } from "#app/enums/species"; +import GameManager from "#app/test/utils/gameManager"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; +import BattleScene from "#app/battle-scene"; +import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; +import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; +import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; +import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { CommandPhase } from "#app/phases/command-phase"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter"; +import { TrainerType } from "#enums/trainer-type"; +import { EggTier } from "#enums/egg-type"; +import { PostMysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; + +const namespace = "mysteryEncounter:expertPokemonBreeder"; +const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA]; +const defaultBiome = Biome.CAVE; +const defaultWave = 45; + +describe("The Expert Pokémon Breeder - Mystery Encounter", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + let scene: BattleScene; + + beforeAll(() => { + phaserGame = new Phaser.Game({ type: Phaser.HEADLESS }); + }); + + beforeEach(async () => { + game = new GameManager(phaserGame); + scene = game.scene; + game.override.mysteryEncounterChance(100); + game.override.startingWave(defaultWave); + game.override.startingBiome(defaultBiome); + game.override.disableTrainerWaves(); + + const biomeMap = new Map([ + [Biome.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]], + ]); + HUMAN_TRANSITABLE_BIOMES.forEach(biome => { + biomeMap.set(biome, [MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER]); + }); + vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + vi.clearAllMocks(); + vi.resetAllMocks(); + }); + + it("should have the correct properties", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + + expect(TheExpertPokemonBreederEncounter.encounterType).toBe(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER); + expect(TheExpertPokemonBreederEncounter.encounterTier).toBe(MysteryEncounterTier.ULTRA); + expect(TheExpertPokemonBreederEncounter.dialogue).toBeDefined(); + expect(TheExpertPokemonBreederEncounter.dialogue.intro).toStrictEqual([ + { + text: `${namespace}.intro` + }, + { + speaker: "trainerNames:expert_pokemon_breeder", + text: `${namespace}.intro_dialogue` + }, + ]); + expect(TheExpertPokemonBreederEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`); + expect(TheExpertPokemonBreederEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`); + expect(TheExpertPokemonBreederEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`); + expect(TheExpertPokemonBreederEncounter.options.length).toBe(3); + }); + + it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => { + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); + game.override.startingBiome(Biome.VOLCANO); + await game.runToMysteryEncounter(); + + expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER); + }); + + it("should initialize fully", async () => { + initSceneWithoutEncounterPhase(scene, defaultParty); + scene.currentBattle.mysteryEncounter = new MysteryEncounter(TheExpertPokemonBreederEncounter); + const encounter = scene.currentBattle.mysteryEncounter!; + scene.currentBattle.waveIndex = defaultWave; + + const { onInit } = encounter; + + expect(encounter.onInit).toBeDefined(); + + encounter.populateDialogueTokensFromRequirements(scene); + const onInitResult = onInit!(scene); + + expect(encounter.enemyPartyConfigs).toBeDefined(); + expect(encounter.enemyPartyConfigs.length).toBe(1); + expect(encounter.enemyPartyConfigs[0].trainerType).toBe(TrainerType.EXPERT_POKEMON_BREEDER); + expect(encounter.enemyPartyConfigs[0].pokemonConfigs?.length).toBe(3); + expect(encounter.spriteConfigs).toBeDefined(); + expect(encounter.spriteConfigs.length).toBe(2); + expect(onInitResult).toBe(true); + }); + + describe("Option 1 - Battle with Pokemon 1", () => { + it("should have the correct properties", () => { + const option = TheExpertPokemonBreederEncounter.options[0]; + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); + expect(option.dialogue).toBeDefined(); + expect(option.dialogue).toStrictEqual({ + buttonLabel: `${namespace}.option.1.label`, + buttonTooltip: expect.any(String), // Varies based on pokemon + selected: [ + { + speaker: "trainerNames:expert_pokemon_breeder", + text: `${namespace}.option.selected`, + }, + ], + }); + }); + + it("should start battle against the trainer", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + await runMysteryEncounterToEnd(game, 1, undefined, true); + + expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.currentBattle.trainer).toBeDefined(); + expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); + expect(scene.getParty().length).toBe(1); + }); + + it("Should reward the player with friendship and eggs based on pokemon selected", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + + const friendshipBefore = scene.currentBattle.mysteryEncounter!.misc.pokemon1.friendship; + + scene.gameData.eggs = []; + const eggsBefore = scene.gameData.eggs; + expect(eggsBefore).toBeDefined(); + const eggsBeforeLength = eggsBefore.length; + + await runMysteryEncounterToEnd(game, 1, undefined, true); + await skipBattleRunMysteryEncounterRewardsPhase(game); + await game.phaseInterceptor.to(SelectModifierPhase, false); + expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + + const eggsAfter = scene.gameData.eggs; + const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon1CommonEggs; + const rareEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon1RareEggs; + expect(eggsAfter).toBeDefined(); + expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); + expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + + game.phaseInterceptor.superEndPhase(); + await game.phaseInterceptor.to(PostMysteryEncounterPhase); + + const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon1.friendship; + expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle + }); + }); + + describe("Option 2 - Battle with Pokemon 2", () => { + it("should have the correct properties", () => { + const option = TheExpertPokemonBreederEncounter.options[1]; + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); + expect(option.dialogue).toBeDefined(); + expect(option.dialogue).toStrictEqual({ + buttonLabel: `${namespace}.option.2.label`, + buttonTooltip: expect.any(String), // Varies based on pokemon + selected: [ + { + speaker: "trainerNames:expert_pokemon_breeder", + text: `${namespace}.option.selected`, + }, + ], + }); + }); + + it("should start battle against the trainer", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + await runMysteryEncounterToEnd(game, 2, undefined, true); + + expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.currentBattle.trainer).toBeDefined(); + expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); + expect(scene.getParty().length).toBe(1); + }); + + it("Should reward the player with friendship and eggs based on pokemon selected", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + + const friendshipBefore = scene.currentBattle.mysteryEncounter!.misc.pokemon2.friendship; + + scene.gameData.eggs = []; + const eggsBefore = scene.gameData.eggs; + expect(eggsBefore).toBeDefined(); + const eggsBeforeLength = eggsBefore.length; + + await runMysteryEncounterToEnd(game, 2, undefined, true); + await skipBattleRunMysteryEncounterRewardsPhase(game); + await game.phaseInterceptor.to(SelectModifierPhase, false); + expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + + const eggsAfter = scene.gameData.eggs; + const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon2CommonEggs; + const rareEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon2RareEggs; + expect(eggsAfter).toBeDefined(); + expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); + expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + + game.phaseInterceptor.superEndPhase(); + await game.phaseInterceptor.to(PostMysteryEncounterPhase); + + const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon2.friendship; + expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle + }); + }); + + describe("Option 3 - Battle with Pokemon 3", () => { + it("should have the correct properties", () => { + const option = TheExpertPokemonBreederEncounter.options[2]; + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); + expect(option.dialogue).toBeDefined(); + expect(option.dialogue).toStrictEqual({ + buttonLabel: `${namespace}.option.3.label`, + buttonTooltip: expect.any(String), // Varies based on pokemon + selected: [ + { + speaker: "trainerNames:expert_pokemon_breeder", + text: `${namespace}.option.selected`, + }, + ], + }); + }); + + it("should start battle against the trainer", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + await runMysteryEncounterToEnd(game, 3, undefined, true); + + expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.currentBattle.trainer).toBeDefined(); + expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); + expect(scene.getParty().length).toBe(1); + }); + + it("Should reward the player with friendship and eggs based on pokemon selected", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + + const friendshipBefore = scene.currentBattle.mysteryEncounter!.misc.pokemon3.friendship; + + scene.gameData.eggs = []; + const eggsBefore = scene.gameData.eggs; + expect(eggsBefore).toBeDefined(); + const eggsBeforeLength = eggsBefore.length; + + await runMysteryEncounterToEnd(game, 3, undefined, true); + await skipBattleRunMysteryEncounterRewardsPhase(game); + await game.phaseInterceptor.to(SelectModifierPhase, false); + expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + + const eggsAfter = scene.gameData.eggs; + const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon3CommonEggs; + const rareEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon3RareEggs; + expect(eggsAfter).toBeDefined(); + expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); + expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + + game.phaseInterceptor.superEndPhase(); + await game.phaseInterceptor.to(PostMysteryEncounterPhase); + + const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon3.friendship; + expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle + }); + }); +}); diff --git a/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts b/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts index c43577337da..e2b1fe8309b 100644 --- a/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts @@ -53,19 +53,22 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { }); it("should have the correct properties", async () => { + const { encounterType, encounterTier, dialogue, options } = ThePokemonSalesmanEncounter; + await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty); - expect(ThePokemonSalesmanEncounter.encounterType).toBe(MysteryEncounterType.THE_POKEMON_SALESMAN); - expect(ThePokemonSalesmanEncounter.encounterTier).toBe(MysteryEncounterTier.ULTRA); - expect(ThePokemonSalesmanEncounter.dialogue).toBeDefined(); - expect(ThePokemonSalesmanEncounter.dialogue.intro).toStrictEqual([ + expect(encounterType).toBe(MysteryEncounterType.THE_POKEMON_SALESMAN); + expect(encounterTier).toBe(MysteryEncounterTier.ULTRA); + expect(dialogue).toBeDefined(); + expect(dialogue.intro).toStrictEqual([ { text: `${namespace}.intro` }, { speaker: `${namespace}.speaker`, text: `${namespace}.intro_dialogue` } ]); - expect(ThePokemonSalesmanEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`); - expect(ThePokemonSalesmanEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`); - expect(ThePokemonSalesmanEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`); - expect(ThePokemonSalesmanEncounter.options.length).toBe(2); + const { title, description, query } = dialogue.encounterOptionsDialogue!; + expect(title).toBe(`${namespace}.title`); + expect(description).toMatch(new RegExp(`^${namespace}\\.description(_shiny)?$`)); + expect(query).toBe(`${namespace}.query`); + expect(options.length).toBe(2); }); it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => { @@ -76,22 +79,6 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_POKEMON_SALESMAN); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_POKEMON_SALESMAN); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully ", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = ThePokemonSalesmanEncounter; @@ -120,12 +107,13 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { describe("Option 1 - Purchase the pokemon", () => { it("should have the correct properties", () => { - const option = ThePokemonSalesmanEncounter.options[0]; - expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT); - expect(option.dialogue).toBeDefined(); - expect(option.dialogue).toStrictEqual({ + const { optionMode, dialogue } = ThePokemonSalesmanEncounter.options[0]; + + expect(optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT); + expect(dialogue).toBeDefined(); + expect(dialogue).toStrictEqual({ buttonLabel: `${namespace}.option.1.label`, - buttonTooltip: `${namespace}.option.1.tooltip`, + buttonTooltip: expect.stringMatching(new RegExp(`^${namespace}\\.option\\.1\\.tooltip(_shiny)?$`)), selected: [ { text: `${namespace}.option.1.selected_message`, diff --git a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index be35ec31784..5c1353ee337 100644 --- a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -83,22 +83,6 @@ describe("The Strong Stuff - Mystery Encounter", () => { expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_STRONG_STUFF); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_STRONG_STUFF); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully ", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = TheStrongStuffEncounter; @@ -114,7 +98,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { expect(TheStrongStuffEncounter.enemyPartyConfigs).toEqual([ { - levelAdditiveMultiplier: 1, + levelAdditiveModifier: 1, disableSwitch: true, pokemonConfigs: [ { diff --git a/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index 0c642225031..1efe6dbd7f8 100644 --- a/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -90,22 +90,6 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_WINSTRATE_CHALLENGE); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_WINSTRATE_CHALLENGE); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = new MysteryEncounter(TheWinstrateChallengeEncounter); diff --git a/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index a4bfaea659a..bfeb249543f 100644 --- a/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -71,22 +71,6 @@ describe("Trash to Treasure - Mystery Encounter", () => { expect(TrashToTreasureEncounter.options.length).toBe(2); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.TRASH_TO_TREASURE); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = TrashToTreasureEncounter; @@ -102,7 +86,7 @@ describe("Trash to Treasure - Mystery Encounter", () => { expect(TrashToTreasureEncounter.enemyPartyConfigs).toEqual([ { - levelAdditiveMultiplier: 1, + levelAdditiveModifier: 1, disableSwitch: true, pokemonConfigs: [ { diff --git a/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index f235609ee08..2f8c4e5111a 100644 --- a/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -74,22 +74,6 @@ describe("Uncommon Breed - Mystery Encounter", () => { expect(UncommonBreedEncounter.options.length).toBe(3); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.UNCOMMON_BREED); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = UncommonBreedEncounter; @@ -107,7 +91,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { expect(onInitResult).toBe(true); }); - describe.skip("Option 1 - Fight", () => { + describe("Option 1 - Fight", () => { it("should have the correct properties", () => { const option = UncommonBreedEncounter.options[0]; expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); @@ -123,7 +107,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { }); }); - it("should start a fight against the boss", async () => { + it.skip("should start a fight against the boss", async () => { const phaseSpy = vi.spyOn(scene, "pushPhase"); const unshiftPhaseSpy = vi.spyOn(scene, "unshiftPhase"); await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty); diff --git a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index ef014c6949b..e532891810c 100644 --- a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -73,22 +73,6 @@ describe("Weird Dream - Mystery Encounter", () => { expect(WeirdDreamEncounter.options.length).toBe(2); }); - it("should not run below wave 10", async () => { - game.override.startingWave(9); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.WEIRD_DREAM); - }); - - it("should not run above wave 179", async () => { - game.override.startingWave(181); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); - }); - it("should initialize fully", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); scene.currentBattle.mysteryEncounter = WeirdDreamEncounter; diff --git a/src/test/mystery-encounter/mystery-encounter.test.ts b/src/test/mystery-encounter/mystery-encounter.test.ts index d2a2e7f9d92..38c999f8aac 100644 --- a/src/test/mystery-encounter/mystery-encounter.test.ts +++ b/src/test/mystery-encounter/mystery-encounter.test.ts @@ -4,10 +4,12 @@ import Phaser from "phaser"; import { Species } from "#enums/species"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import BattleScene from "#app/battle-scene"; describe("Mystery Encounters", () => { let phaserGame: Phaser.Game; let game: GameManager; + let scene: BattleScene; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -21,6 +23,7 @@ describe("Mystery Encounters", () => { beforeEach(() => { game = new GameManager(phaserGame); + scene = game.scene; game.override.startingWave(11); game.override.mysteryEncounterChance(100); }); @@ -32,23 +35,20 @@ describe("Mystery Encounters", () => { expect(game.scene.getCurrentPhase()!.constructor.name).toBe(MysteryEncounterPhase.name); }); - it("", async () => { - await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, [Species.CHARIZARD, Species.VOLCARONA]); + it("Encounters should not run below wave 10", async () => { + game.override.startingWave(9); - await game.phaseInterceptor.to(MysteryEncounterPhase, false); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(MysteryEncounterPhase.name); + await game.runToMysteryEncounter(); + + expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.MYSTERIOUS_CHALLENGERS); }); - it("spawns mysterious challengers encounter", async () => { - }); + it("Encounters should not run above wave 180", async () => { + game.override.startingWave(181); - it("spawns mysterious chest encounter", async () => { - }); + await game.runToMysteryEncounter(); - it("spawns dark deal encounter", async () => { - }); - - it("spawns fight or flight encounter", async () => { + expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); }); }); diff --git a/src/test/reload.test.ts b/src/test/reload.test.ts index 7c4523dd9ef..5009d76d1a7 100644 --- a/src/test/reload.test.ts +++ b/src/test/reload.test.ts @@ -35,7 +35,7 @@ describe("Reload", () => { expect(preReloadRngState).toBe(postReloadRngState); }, 20000); - it("should not have RNG inconsistencies after a biome switch", async () => { + it.each(Array.from({length: 100}))("should not have RNG inconsistencies after a biome switch", async () => { game.override .startingWave(10) .battleType("single") diff --git a/src/test/vitest.setup.ts b/src/test/vitest.setup.ts index 3bb5c240d94..74129f20d26 100644 --- a/src/test/vitest.setup.ts +++ b/src/test/vitest.setup.ts @@ -14,6 +14,8 @@ import { initStatsKeys } from "#app/ui/game-stats-ui-handler"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { beforeAll, vi } from "vitest"; +process.env.TZ = "UTC"; + /** Mock the override import to always return default values, ignoring any custom overrides. */ vi.mock("#app/overrides", async (importOriginal) => { const { defaultOverrides } = await importOriginal(); diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index 2c326bafe98..56621c6a944 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -346,6 +346,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { super.clear(); this.config = null; this.optionSelectContainer.setVisible(false); + this.scrollCursor = 0; this.eraseCursor(); } diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index 605b8c538a9..491c4acb523 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -1,12 +1,13 @@ -import BattleScene from "../battle-scene"; +import BattleScene from "#app/battle-scene"; import { Button } from "#enums/buttons"; import i18next from "i18next"; -import { Achv, achvs, getAchievementDescription } from "../system/achv"; -import { Voucher, getVoucherTypeIcon, getVoucherTypeName, vouchers } from "../system/voucher"; -import MessageUiHandler from "./message-ui-handler"; -import { addTextObject, TextStyle } from "./text"; -import { Mode } from "./ui"; -import { addWindow } from "./ui-theme"; +import { Achv, achvs, getAchievementDescription } from "#app/system/achv"; +import { Voucher, getVoucherTypeIcon, getVoucherTypeName, vouchers } from "#app/system/voucher"; +import MessageUiHandler from "#app/ui/message-ui-handler"; +import { addTextObject, TextStyle } from "#app/ui/text"; +import { Mode } from "#app/ui/ui"; +import { addWindow } from "#app/ui/ui-theme"; +import { ScrollBar } from "#app/ui/scroll-bar"; import { PlayerGender } from "#enums/player-gender"; enum Page { @@ -49,6 +50,7 @@ export default class AchvsUiHandler extends MessageUiHandler { private vouchersTotal: number; private currentTotal: number; + private scrollBar: ScrollBar; private scrollCursor: number; private cursorObj: Phaser.GameObjects.NineSlice | null; private currentPage: Page; @@ -91,7 +93,10 @@ export default class AchvsUiHandler extends MessageUiHandler { this.iconsBg = addWindow(this.scene, 0, this.headerBg.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - this.headerBg.height - 68); this.iconsBg.setOrigin(0, 0); - this.iconsContainer = this.scene.add.container(6, this.headerBg.height + 6); + const yOffset = 6; + this.scrollBar = new ScrollBar(this.scene, this.iconsBg.width - 9, this.iconsBg.y + yOffset, 4, this.iconsBg.height - yOffset * 2, this.ROWS); + + this.iconsContainer = this.scene.add.container(5, this.headerBg.height + 8); this.icons = []; @@ -148,6 +153,7 @@ export default class AchvsUiHandler extends MessageUiHandler { this.mainContainer.add(this.headerText); this.mainContainer.add(this.headerActionText); this.mainContainer.add(this.iconsBg); + this.mainContainer.add(this.scrollBar); this.mainContainer.add(this.iconsContainer); this.mainContainer.add(titleBg); this.mainContainer.add(this.titleText); @@ -162,6 +168,7 @@ export default class AchvsUiHandler extends MessageUiHandler { this.currentPage = Page.ACHIEVEMENTS; this.setCursor(0); + this.setScrollCursor(0); this.mainContainer.setVisible(false); } @@ -175,6 +182,8 @@ export default class AchvsUiHandler extends MessageUiHandler { this.mainContainer.setVisible(true); this.setCursor(0); this.setScrollCursor(0); + this.scrollBar.setTotalRows(Math.ceil(this.currentTotal / this.COLS)); + this.scrollBar.setScrollCursor(0); this.getUi().moveTo(this.mainContainer, this.getUi().length - 1); @@ -224,6 +233,8 @@ export default class AchvsUiHandler extends MessageUiHandler { this.updateAchvIcons(); } this.setCursor(0, true); + this.scrollBar.setTotalRows(Math.ceil(this.currentTotal / this.COLS)); + this.scrollBar.setScrollCursor(0); this.mainContainer.update(); } if (button === Button.CANCEL) { @@ -237,32 +248,44 @@ export default class AchvsUiHandler extends MessageUiHandler { if (this.cursor < this.COLS) { if (this.scrollCursor) { success = this.setScrollCursor(this.scrollCursor - 1); + } else { + // Wrap around to the last row + success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS); + let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS; + if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS -1) { + newCursorIndex -= this.COLS; + } + success = success && this.setCursor(newCursorIndex); } } else { success = this.setCursor(this.cursor - this.COLS); } break; case Button.DOWN: - const canMoveDown = (this.cursor + itemOffset) + this.COLS < this.currentTotal; + const canMoveDown = itemOffset + 1 < this.currentTotal; if (rowIndex >= this.ROWS - 1) { if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) { + // scroll down one row success = this.setScrollCursor(this.scrollCursor + 1); + } else { + // wrap back to the first row + success = this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS); } } else if (canMoveDown) { - success = this.setCursor(this.cursor + this.COLS); + success = this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1)); } break; case Button.LEFT: - if (!this.cursor && this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1) && this.setCursor(this.cursor + (this.COLS - 1)); - } else if (this.cursor) { + if (this.cursor % this.COLS === 0) { + success = this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1)); + } else { success = this.setCursor(this.cursor - 1); } break; case Button.RIGHT: - if (this.cursor + 1 === this.ROWS * this.COLS && this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS) { - success = this.setScrollCursor(this.scrollCursor + 1) && this.setCursor(this.cursor - (this.COLS - 1)); - } else if (this.cursor + itemOffset < this.currentTotal - 1) { + if ((this.cursor + 1) % this.COLS === 0 || (this.cursor + itemOffset) === (this.currentTotal - 1)) { + success = this.setCursor(this.cursor - this.cursor % this.COLS); + } else { success = this.setCursor(this.cursor + 1); } break; @@ -315,15 +338,22 @@ export default class AchvsUiHandler extends MessageUiHandler { } this.scrollCursor = scrollCursor; + this.scrollBar.setScrollCursor(this.scrollCursor); + + // Cursor cannot go farther than the last element in the list + const maxCursor = Math.min(this.cursor, this.currentTotal - this.scrollCursor * this.COLS - 1); + if (maxCursor !== this.cursor) { + this.setCursor(maxCursor); + } switch (this.currentPage) { case Page.ACHIEVEMENTS: this.updateAchvIcons(); - this.showAchv(achvs[Object.keys(achvs)[Math.min(this.cursor + this.scrollCursor * this.COLS, Object.values(achvs).length - 1)]]); + this.showAchv(achvs[Object.keys(achvs)[this.cursor + this.scrollCursor * this.COLS]]); break; case Page.VOUCHERS: this.updateVoucherIcons(); - this.showVoucher(vouchers[Object.keys(vouchers)[Math.min(this.cursor + this.scrollCursor * this.COLS, Object.values(vouchers).length - 1)]]); + this.showVoucher(vouchers[Object.keys(vouchers)[this.cursor + this.scrollCursor * this.COLS]]); break; } return true; @@ -411,6 +441,7 @@ export default class AchvsUiHandler extends MessageUiHandler { super.clear(); this.currentPage = Page.ACHIEVEMENTS; this.mainContainer.setVisible(false); + this.setScrollCursor(0); this.eraseCursor(); } diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 1d598f6ac4e..b3474bed5cd 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -381,17 +381,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container { const ownedAbilityAttrs = pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr; - let playerOwnsThisAbility = false; // Check if the player owns ability for the root form - if ((ownedAbilityAttrs & 1) > 0 && pokemon.hasSameAbilityInRootForm(0)) { - playerOwnsThisAbility = true; - } - if ((ownedAbilityAttrs & 2) > 0 && pokemon.hasSameAbilityInRootForm(1)) { - playerOwnsThisAbility = true; - } - if ((ownedAbilityAttrs & 4) > 0 && pokemon.hasSameAbilityInRootForm(2)) { - playerOwnsThisAbility = true; - } + const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs); if (missingDexAttrs || !playerOwnsThisAbility) { this.ownedIcon.setTint(0x808080); diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index c9d3f195720..a1e10d74c64 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -160,7 +160,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.player = args[0]; - const partyHasHeldItem = this.player && !!this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferrable).length; + const partyHasHeldItem = this.player && !!this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable).length; const canLockRarities = !!this.scene.findModifier(m => m instanceof LockModifierTiersModifier); this.transferButtonContainer.setVisible(false); @@ -277,13 +277,13 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.lockRarityButtonContainer.setVisible(canLockRarities); this.scene.tweens.add({ - targets: [ this.lockRarityButtonContainer, this.checkButtonContainer, this.continueButtonContainer ], + targets: [ this.checkButtonContainer, this.continueButtonContainer ], alpha: 1, duration: 250 }); this.scene.tweens.add({ - targets: [this.rerollButtonContainer], + targets: [this.rerollButtonContainer, this.lockRarityButtonContainer], alpha: this.rerollCost < 0 ? 0.5 : 1, duration: 250 }); diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 307bab0a3af..08de740e3ec 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -6,7 +6,7 @@ import { Button } from "#enums/buttons"; import { addWindow, WindowVariant } from "./ui-theme"; import { MysteryEncounterPhase } from "../phases/mystery-encounter-phases"; import { PartyUiMode } from "./party-ui-handler"; -import MysteryEncounterOption from "../data/mystery-encounters/mystery-encounter-option"; +import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import * as Utils from "../utils"; import { isNullOrUndefined } from "../utils"; import { getPokeballAtlasKey } from "../data/pokeball"; @@ -42,7 +42,8 @@ export default class MysteryEncounterUiHandler extends UiHandler { private encounterOptions: MysteryEncounterOption[] = []; private optionsMeetsReqs: boolean[]; - protected viewPartyIndex: integer = 0; + protected viewPartyIndex: number = 0; + protected viewPartyXPosition: number = 0; protected blockInput: boolean = true; @@ -300,11 +301,11 @@ export default class MysteryEncounterUiHandler extends UiHandler { } } - override getCursor(): integer { + override getCursor(): number { return this.cursor ? this.cursor : 0; } - override setCursor(cursor: integer): boolean { + override setCursor(cursor: number): boolean { const prevCursor = this.getCursor(); const changed = prevCursor !== cursor; if (changed) { @@ -319,7 +320,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { } if (cursor === this.viewPartyIndex) { - this.cursorObj.setPosition(246, -17); + this.cursorObj.setPosition(this.viewPartyXPosition, -17); } else if (this.optionsContainer.getAll()?.length === 3) { // 2 Options this.cursorObj.setPosition(-10.5 + (cursor % 2 === 1 ? 100 : 0), 15); } else if (this.optionsContainer.getAll()?.length === 4) { // 3 Options @@ -419,8 +420,10 @@ export default class MysteryEncounterUiHandler extends UiHandler { } // View Party Button - const viewPartyText = addBBCodeTextObject(this.scene, 256, -24, getBBCodeFrag(i18next.t("mysteryEncounterMessages:view_party_button"), TextStyle.PARTY), TextStyle.PARTY); + const viewPartyText = addBBCodeTextObject(this.scene, (this.scene.game.canvas.width) / 6, -24, getBBCodeFrag(i18next.t("mysteryEncounterMessages:view_party_button"), TextStyle.PARTY), TextStyle.PARTY); this.optionsContainer.add(viewPartyText); + viewPartyText.x -= (viewPartyText.displayWidth + 16); + this.viewPartyXPosition = viewPartyText.x - 10; // Description Window const titleTextObject = addBBCodeTextObject(this.scene, 0, 0, titleText ?? "", TextStyle.TOOLTIP_TITLE, { wordWrap: { width: 750 }, align: "center", lineSpacing: -8 }); diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 8c777350964..6b6ce2aa789 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -355,7 +355,7 @@ export default class PartyUiHandler extends MessageUiHandler { const newPokemon = this.scene.getParty()[p]; // this next line gets all of the transferable items from pokemon [p]; it does this by getting all the held modifiers that are transferable and checking to see if they belong to pokemon [p] const getTransferrableItemsFromPokemon = (newPokemon: PlayerPokemon) => - this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).isTransferrable && (m as PokemonHeldItemModifier).pokemonId === newPokemon.id) as PokemonHeldItemModifier[]; + this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).isTransferable && (m as PokemonHeldItemModifier).pokemonId === newPokemon.id) as PokemonHeldItemModifier[]; // this next bit checks to see if the the selected item from the original transfer pokemon exists on the new pokemon [p]; this returns undefined if the new pokemon doesn't have the item at all, otherwise it returns the pokemonHeldItemModifier for that item const matchingModifier = newPokemon.scene.findModifier(m => m instanceof PokemonHeldItemModifier && m.pokemonId === newPokemon.id && m.matchType(getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor])) as PokemonHeldItemModifier; const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us @@ -399,7 +399,7 @@ export default class PartyUiHandler extends MessageUiHandler { || (option === PartyOption.RELEASE && this.partyUiMode === PartyUiMode.RELEASE)) { let filterResult: string | null; const getTransferrableItemsFromPokemon = (pokemon: PlayerPokemon) => - this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferrable && m.pokemonId === pokemon.id) as PokemonHeldItemModifier[]; + this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id) as PokemonHeldItemModifier[]; if (option !== PartyOption.TRANSFER && option !== PartyOption.SPLICE) { filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); if (filterResult === null && (option === PartyOption.SEND_OUT || option === PartyOption.PASS_BATON)) { @@ -596,7 +596,7 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode) { /** Initialize item quantities for the selected Pokemon */ const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.isTransferrable && m.pokemonId === this.scene.getParty()[this.cursor].id) as PokemonHeldItemModifier[]; + && m.isTransferable && m.pokemonId === this.scene.getParty()[this.cursor].id) as PokemonHeldItemModifier[]; this.transferQuantities = itemModifiers.map(item => item.getStackCount()); this.transferQuantitiesMax = itemModifiers.map(item => item.getStackCount()); } @@ -813,7 +813,7 @@ export default class PartyUiHandler extends MessageUiHandler { const itemModifiers = this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER ? this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.isTransferrable && m.pokemonId === pokemon.id) as PokemonHeldItemModifier[] + && m.isTransferable && m.pokemonId === pokemon.id) as PokemonHeldItemModifier[] : []; if (this.options.length) { diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 3c54e529d43..e9ad2a26c15 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -267,18 +267,13 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonAbilityText.setColor(getTextColor(abilityTextStyle, false, this.scene.uiTheme)); this.pokemonAbilityText.setShadowColor(getTextColor(abilityTextStyle, true, this.scene.uiTheme)); - /** - * If the opposing Pokemon only has 1 normal ability and is using the hidden ability it should have the same behavior - * if it had 2 normal abilities. This code checks if that is the case and uses the correct opponent Pokemon abilityIndex (2) - * for calculations so it aligns with where the hidden ability is stored in the starter data's abilityAttr (4) - */ - const opponentPokemonOneNormalAbility = (pokemon.species.getAbilityCount() === 2); - const opponentPokemonAbilityIndex = (opponentPokemonOneNormalAbility && pokemon.abilityIndex === 1) ? 2 : pokemon.abilityIndex; - const opponentPokemonAbilityAttr = 1 << opponentPokemonAbilityIndex; - const rootFormHasHiddenAbility = starterEntry.abilityAttr & opponentPokemonAbilityAttr; + const ownedAbilityAttrs = pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr; - if (!rootFormHasHiddenAbility) { + // Check if the player owns ability for the root form + const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs); + + if (!playerOwnsThisAbility) { this.pokemonAbilityLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); this.pokemonAbilityLabelText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); } else { diff --git a/src/ui/run-history-ui-handler.ts b/src/ui/run-history-ui-handler.ts index 8f132a1ab1c..d983fb0b0b8 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -13,7 +13,7 @@ import { RunEntry } from "../system/game-data"; import { PlayerGender } from "#enums/player-gender"; import { TrainerVariant } from "../field/trainer"; -export type RunSelectCallback = (cursor: integer) => void; +export type RunSelectCallback = (cursor: number) => void; export const RUN_HISTORY_LIMIT: number = 25; @@ -25,15 +25,15 @@ export const RUN_HISTORY_LIMIT: number = 25; */ export default class RunHistoryUiHandler extends MessageUiHandler { + private readonly maxRows = 3; + private runSelectContainer: Phaser.GameObjects.Container; private runsContainer: Phaser.GameObjects.Container; - private runSelectMessageBox: Phaser.GameObjects.NineSlice; - private runSelectMessageBoxContainer: Phaser.GameObjects.Container; private runs: RunEntryContainer[]; private runSelectCallback: RunSelectCallback | null; - private scrollCursor: integer = 0; + private scrollCursor: number = 0; private cursorObj: Phaser.GameObjects.NineSlice | null; @@ -74,15 +74,15 @@ export default class RunHistoryUiHandler extends MessageUiHandler { this.getUi().bringToTop(this.runSelectContainer); this.runSelectContainer.setVisible(true); - this.populateRuns(this.scene); + this.populateRuns(this.scene).then(() => { + this.setScrollCursor(0); + this.setCursor(0); - this.setScrollCursor(0); - this.setCursor(0); - - //Destroys the cursor if there are no runs saved so far. - if (this.runs.length === 0) { - this.clearCursor(); - } + //Destroys the cursor if there are no runs saved so far. + if (this.runs.length === 0) { + this.clearCursor(); + } + }); return true; } @@ -122,13 +122,21 @@ export default class RunHistoryUiHandler extends MessageUiHandler { success = this.setCursor(this.cursor - 1); } else if (this.scrollCursor) { success = this.setScrollCursor(this.scrollCursor - 1); + } else if (this.runs.length > 1) { + // wrap around to the bottom + success = this.setCursor(Math.min(this.runs.length - 1, this.maxRows - 1)); + success = this.setScrollCursor(Math.max(0, this.runs.length - this.maxRows)) || success; } break; case Button.DOWN: - if (this.cursor < 2) { + if (this.cursor < Math.min(this.maxRows - 1, this.runs.length - this.scrollCursor - 1)) { success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.runs.length - 3) { + } else if (this.scrollCursor < this.runs.length - this.maxRows) { success = this.setScrollCursor(this.scrollCursor + 1); + } else if (this.runs.length > 1) { + // wrap around to the top + success = this.setCursor(0); + success = this.setScrollCursor(0) || success; } break; } @@ -218,6 +226,7 @@ export default class RunHistoryUiHandler extends MessageUiHandler { override clear() { super.clear(); this.runSelectContainer.setVisible(false); + this.setScrollCursor(0); this.clearCursor(); this.runSelectCallback = null; this.clearRuns(); @@ -281,7 +290,7 @@ class RunEntryContainer extends Phaser.GameObjects.Container { const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; const genderStr = PlayerGender[genderIndex].toLowerCase(); // Defeats from wild Pokemon battles will show the Pokemon responsible by the text of the run result. - if (data.battleType === BattleType.WILD) { + if (data.battleType === BattleType.WILD || (data.battleType === BattleType.MYSTERY_ENCOUNTER && !data.trainer)) { const enemyContainer = this.scene.add.container(8, 5); const gameOutcomeLabel = addTextObject(this.scene, 0, 0, `${i18next.t("runHistory:defeatedWild", { context: genderStr })}`, TextStyle.WINDOW); enemyContainer.add(gameOutcomeLabel); @@ -302,7 +311,7 @@ class RunEntryContainer extends Phaser.GameObjects.Container { enemy.destroy(); }); this.add(enemyContainer); - } else if (data.battleType === BattleType.TRAINER) { // Defeats from Trainers show the trainer's title and name + } else if (data.battleType === BattleType.TRAINER || (data.battleType === BattleType.MYSTERY_ENCOUNTER && data.trainer)) { // Defeats from Trainers show the trainer's title and name const tObj = data.trainer.toTrainer(this.scene); // Because of the interesting mechanics behind rival names, the rival name and title have to be retrieved differently const RIVAL_TRAINER_ID_THRESHOLD = 375; @@ -360,7 +369,7 @@ class RunEntryContainer extends Phaser.GameObjects.Container { // The code here does not account for icon weirdness. const pokemonIconsContainer = this.scene.add.container(140, 17); - data.party.forEach((p: PokemonData, i: integer) => { + data.party.forEach((p: PokemonData, i: number) => { const iconContainer = this.scene.add.container(26 * i, 0); iconContainer.setScale(0.75); const pokemon = p.toPokemon(this.scene); diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 8f0437002d4..119b7bc9c4a 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -49,15 +49,11 @@ export default class RunInfoUiHandler extends UiHandler { private runResultContainer: Phaser.GameObjects.Container; private runInfoContainer: Phaser.GameObjects.Container; private partyContainer: Phaser.GameObjects.Container; - private partyHeldItemsContainer: Phaser.GameObjects.Container; private statsBgWidth: integer; - private partyContainerHeight: integer; - private partyContainerWidth: integer; private hallofFameContainer: Phaser.GameObjects.Container; private endCardContainer: Phaser.GameObjects.Container; - private partyInfo: Phaser.GameObjects.Container[]; private partyVisibility: Boolean; private modifiersModule: any; @@ -211,7 +207,7 @@ export default class RunInfoUiHandler extends UiHandler { if (!this.isVictory) { const enemyContainer = this.scene.add.container(0, 0); // Wild - Single and Doubles - if (this.runInfo.battleType === BattleType.WILD) { + if (this.runInfo.battleType === BattleType.WILD || (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER && !this.runInfo.trainer)) { switch (this.runInfo.enemyParty.length) { case 1: // Wild - Singles @@ -222,7 +218,7 @@ export default class RunInfoUiHandler extends UiHandler { this.parseWildDoubleDefeat(enemyContainer); break; } - } else if (this.runInfo.battleType === BattleType.TRAINER) { + } else if (this.runInfo.battleType === BattleType.TRAINER || (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER && this.runInfo.trainer)) { this.parseTrainerDefeat(enemyContainer); } this.runResultContainer.add(enemyContainer); @@ -381,10 +377,6 @@ export default class RunInfoUiHandler extends UiHandler { break; case GameModes.SPLICED_ENDLESS: modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false); - if (this.runInfo.waveIndex === this.scene.gameData.gameStats.highestEndlessWave) { - modeText.appendText(` [${i18next.t("runHistory:personalBest")}]`, false); - modeText.setTint(0xffef5c, 0x47ff69, 0x6b6bff, 0xff6969); - } break; case GameModes.CHALLENGE: modeText.appendText(`${i18next.t("gameMode:challenge")}`, false); @@ -403,17 +395,18 @@ export default class RunInfoUiHandler extends UiHandler { break; case GameModes.ENDLESS: modeText.appendText(`${i18next.t("gameMode:endless")}`, false); - // If the player achieves a personal best in Endless, the mode text will be tinted similarly to SSS luck to celebrate their achievement. - if (this.runInfo.waveIndex === this.scene.gameData.gameStats.highestEndlessWave) { - modeText.appendText(` [${i18next.t("runHistory:personalBest")}]`, false); - modeText.setTint(0xffef5c, 0x47ff69, 0x6b6bff, 0xff6969); - } break; case GameModes.CLASSIC: modeText.appendText(`${i18next.t("gameMode:classic")}`, false); break; } + // If the player achieves a personal best in Endless, the mode text will be tinted similarly to SSS luck to celebrate their achievement. + if ((this.runInfo.gameMode === GameModes.ENDLESS || this.runInfo.gameMode === GameModes.SPLICED_ENDLESS) && this.runInfo.waveIndex === this.scene.gameData.gameStats.highestEndlessWave) { + modeText.appendText(` [${i18next.t("runHistory:personalBest")}]`); + modeText.setTint(0xffef5c, 0x47ff69, 0x6b6bff, 0xff6969); + } + // Duration + Money const runInfoTextContainer = this.scene.add.container(0, 0); // Japanese is set to a greater line spacing of 35px in addBBCodeTextObject() if lineSpacing < 12. @@ -866,7 +859,7 @@ export default class RunInfoUiHandler extends UiHandler { private buttonCycleOption(button: Button) { switch (button) { case Button.CYCLE_FORM: - if (this.isVictory) { + if (this.isVictory && this.pageMode !== RunInfoUiMode.HALL_OF_FAME) { if (!this.endCardContainer || !this.endCardContainer.visible) { this.createVictorySplash(); this.endCardContainer.setVisible(true); @@ -880,7 +873,7 @@ export default class RunInfoUiHandler extends UiHandler { } break; case Button.CYCLE_SHINY: - if (this.isVictory) { + if (this.isVictory && this.pageMode !== RunInfoUiMode.ENDING_ART) { if (!this.hallofFameContainer.visible) { this.hallofFameContainer.setVisible(true); this.pageMode = RunInfoUiMode.HALL_OF_FAME; @@ -891,7 +884,7 @@ export default class RunInfoUiHandler extends UiHandler { } break; case Button.CYCLE_ABILITY: - if (this.runInfo.modifiers.length !== 0) { + if (this.runInfo.modifiers.length !== 0 && this.pageMode === RunInfoUiMode.MAIN) { if (this.partyVisibility) { this.showParty(false); } else { diff --git a/src/ui/scroll-bar.ts b/src/ui/scroll-bar.ts index e756393ae1a..5ed79d0cdad 100644 --- a/src/ui/scroll-bar.ts +++ b/src/ui/scroll-bar.ts @@ -1,36 +1,65 @@ +/** + * A vertical scrollbar element that resizes dynamically based on the current scrolling + * and number of elements that can be shown on screen + */ export class ScrollBar extends Phaser.GameObjects.Container { - private bg: Phaser.GameObjects.Image; + private bg: Phaser.GameObjects.NineSlice; private handleBody: Phaser.GameObjects.Rectangle; - private handleBottom: Phaser.GameObjects.Image; - private pages: number; - private page: number; + private handleBottom: Phaser.GameObjects.NineSlice; + private currentRow: number; + private totalRows: number; + private maxRows: number; - constructor(scene: Phaser.Scene, x: number, y: number, pages: number) { + /** + * @param scene the current scene + * @param x the scrollbar's x position (origin: top left) + * @param y the scrollbar's y position (origin: top left) + * @param width the scrollbar's width + * @param height the scrollbar's height + * @param maxRows the maximum number of rows that can be shown at once + */ + constructor(scene: Phaser.Scene, x: number, y: number, width: number, height: number, maxRows: number) { super(scene, x, y); - this.bg = scene.add.image(0, 0, "scroll_bar"); + this.maxRows = maxRows; + + const borderSize = 2; + width = Math.max(width, 4); + + this.bg = scene.add.nineslice(0, 0, "scroll_bar", undefined, width, height, borderSize, borderSize, borderSize, borderSize); this.bg.setOrigin(0, 0); this.add(this.bg); - this.handleBody = scene.add.rectangle(1, 1, 3, 4, 0xaaaaaa); + this.handleBody = scene.add.rectangle(1, 1, width - 2, 4, 0xaaaaaa); this.handleBody.setOrigin(0, 0); this.add(this.handleBody); - this.handleBottom = scene.add.image(1, 1, "scroll_bar_handle"); + this.handleBottom = scene.add.nineslice(1, 1, "scroll_bar_handle", undefined, width - 2, 2, 2, 0, 0, 0); this.handleBottom.setOrigin(0, 0); this.add(this.handleBottom); } - setPage(page: number): void { - this.page = page; - this.handleBody.y = 1 + (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.pages * page; + /** + * Set the current row that is displayed + * Moves the bar handle up or down accordingly + * @param scrollCursor how many times the view was scrolled down + */ + setScrollCursor(scrollCursor: number): void { + this.currentRow = scrollCursor; + this.handleBody.y = 1 + (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.totalRows * this.currentRow; this.handleBottom.y = this.handleBody.y + this.handleBody.displayHeight; } - setPages(pages: number): void { - this.pages = pages; - this.handleBody.height = (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) * 9 / this.pages; + /** + * Set the total number of rows to display + * If it's smaller than the maximum number of rows on screen the bar will get hidden + * Otherwise the scrollbar handle gets resized based on the ratio to the maximum number of rows + * @param rows how many rows of data there are in total + */ + setTotalRows(rows: number): void { + this.totalRows = rows; + this.handleBody.height = (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) * this.maxRows / this.totalRows; - this.setVisible(this.pages > 9); + this.setVisible(this.totalRows > this.maxRows); } } diff --git a/src/ui/settings/abstract-control-settings-ui-handler.ts b/src/ui/settings/abstract-control-settings-ui-handler.ts index f8dab1bf7cc..efa262bb2e9 100644 --- a/src/ui/settings/abstract-control-settings-ui-handler.ts +++ b/src/ui/settings/abstract-control-settings-ui-handler.ts @@ -1,11 +1,12 @@ -import UiHandler from "../ui-handler"; -import BattleScene from "../../battle-scene"; -import {Mode} from "../ui"; -import {InterfaceConfig} from "../../inputs-controller"; -import {addWindow} from "../ui-theme"; -import {addTextObject, TextStyle} from "../text"; -import {getIconWithSettingName} from "#app/configs/inputs/configHandler"; -import NavigationMenu, {NavigationManager} from "#app/ui/settings/navigationMenu"; +import UiHandler from "#app/ui/ui-handler"; +import BattleScene from "#app/battle-scene"; +import { Mode } from "#app/ui/ui"; +import { InterfaceConfig } from "#app/inputs-controller"; +import { addWindow } from "#app/ui/ui-theme"; +import { addTextObject, TextStyle } from "#app/ui/text"; +import { ScrollBar } from "#app/ui/scroll-bar"; +import { getIconWithSettingName } from "#app/configs/inputs/configHandler"; +import NavigationMenu, { NavigationManager } from "#app/ui/settings/navigationMenu"; import { Device } from "#enums/devices"; import { Button } from "#enums/buttons"; import i18next from "i18next"; @@ -19,7 +20,7 @@ export interface LayoutConfig { inputsIcons: InputsIcons; settingLabels: Phaser.GameObjects.Text[]; optionValueLabels: Phaser.GameObjects.Text[][]; - optionCursors: integer[]; + optionCursors: number[]; keys: string[]; bindingSettings: Array; } @@ -31,8 +32,9 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler protected optionsContainer: Phaser.GameObjects.Container; protected navigationContainer: NavigationMenu; - protected scrollCursor: integer; - protected optionCursors: integer[]; + protected scrollBar: ScrollBar; + protected scrollCursor: number; + protected optionCursors: number[]; protected cursorObj: Phaser.GameObjects.NineSlice | null; protected optionsBg: Phaser.GameObjects.NineSlice; @@ -65,7 +67,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler protected device: Device; abstract saveSettingToLocalStorage(setting, cursor): void; - abstract setSetting(scene: BattleScene, setting, value: integer): boolean; + abstract setSetting(scene: BattleScene, setting, value: number): boolean; /** * Constructor for the AbstractSettingsUiHandler. @@ -241,7 +243,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler // Calculate the total available space for placing option labels next to their setting label // We reserve space for the setting label and then distribute the remaining space evenly - const totalSpace = (300 - labelWidth) - totalWidth / 6; + const totalSpace = (297 - labelWidth) - totalWidth / 6; // Calculate the spacing between options based on the available space divided by the number of gaps between labels const optionSpacing = Math.floor(totalSpace / (optionValueLabels[s].length - 1)); @@ -269,6 +271,11 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler // Add the options container to the overall settings container to be displayed in the UI. this.settingsContainer.add(optionsContainer); } + + // Add vertical scrollbar + this.scrollBar = new ScrollBar(this.scene, this.optionsBg.width - 9, this.optionsBg.y + 5, 4, this.optionsBg.height - 11, this.rowsToDisplay); + this.settingsContainer.add(this.scrollBar); + // Add the settings container to the UI. ui.add(this.settingsContainer); @@ -413,6 +420,8 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler this.optionCursors = layout.optionCursors; this.inputsIcons = layout.inputsIcons; this.bindingSettings = layout.bindingSettings; + this.scrollBar.setTotalRows(layout.settingLabels.length); + this.scrollBar.setScrollCursor(0); // Return true indicating the layout was successfully applied. return true; @@ -538,7 +547,7 @@ export default abstract class AbstractControlSettingsUiHandler 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 { const ret = super.setCursor(cursor); // If the optionsContainer is not initialized, return the result from the parent class directly. if (!this.optionsContainer) { @@ -547,7 +556,8 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler // Check if the cursor object exists, if not, create it. if (!this.cursorObj) { - this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, (this.scene.game.canvas.width / 6) - 10, 16, 1, 1, 1, 1); + const cursorWidth = (this.scene.game.canvas.width / 6) - (this.scrollBar.visible? 16 : 10); + this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, cursorWidth, 16, 1, 1, 1, 1); this.cursorObj.setOrigin(0, 0); // Set the origin to the top-left corner. this.optionsContainer.add(this.cursorObj); // Add the cursor to the options container. } @@ -564,7 +574,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler * @param scrollCursor - The scroll cursor position to set. * @returns `true` if the scroll cursor was set successfully. */ - setScrollCursor(scrollCursor: integer): boolean { + setScrollCursor(scrollCursor: number): boolean { // Check if the new scroll position is the same as the current one; if so, do not update. if (scrollCursor === this.scrollCursor) { return false; @@ -572,6 +582,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler // Update the internal scroll cursor state this.scrollCursor = scrollCursor; + this.scrollBar.setScrollCursor(this.scrollCursor); // Apply the new scroll position to the settings UI. this.updateSettingsScroll(); @@ -590,7 +601,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler * @param save - Whether to save the setting to local storage. * @returns `true` if the option cursor was set successfully. */ - setOptionCursor(settingIndex: integer, cursor: integer, save?: boolean): boolean { + setOptionCursor(settingIndex: number, cursor: number, save?: boolean): boolean { // Retrieve the specific setting using the settingIndex from the settingDevice enumeration. const setting = this.setting[Object.keys(this.setting)[settingIndex]]; diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 570377eab43..975a32127ff 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -1,12 +1,13 @@ -import BattleScene from "../../battle-scene"; -import { hasTouchscreen, isMobile } from "../../touch-controls"; -import { TextStyle, addTextObject } from "../text"; -import { Mode } from "../ui"; -import UiHandler from "../ui-handler"; -import { addWindow } from "../ui-theme"; -import {Button} from "#enums/buttons"; -import {InputsIcons} from "#app/ui/settings/abstract-control-settings-ui-handler"; -import NavigationMenu, {NavigationManager} from "#app/ui/settings/navigationMenu"; +import BattleScene from "#app/battle-scene"; +import { hasTouchscreen, isMobile } from "#app/touch-controls"; +import { TextStyle, addTextObject } from "#app/ui/text"; +import { Mode } from "#app/ui/ui"; +import UiHandler from "#app/ui/ui-handler"; +import { addWindow } from "#app/ui/ui-theme"; +import { ScrollBar } from "#app/ui/scroll-bar"; +import { Button } from "#enums/buttons"; +import { InputsIcons } from "#app/ui/settings/abstract-control-settings-ui-handler"; +import NavigationMenu, { NavigationManager } from "#app/ui/settings/navigationMenu"; import { Setting, SettingKeys, SettingType } from "#app/system/settings/settings"; import i18next from "i18next"; @@ -19,11 +20,12 @@ export default class AbstractSettingsUiHandler extends UiHandler { private optionsContainer: Phaser.GameObjects.Container; private navigationContainer: NavigationMenu; - private scrollCursor: integer; + private scrollCursor: number; + private scrollBar: ScrollBar; private optionsBg: Phaser.GameObjects.NineSlice; - private optionCursors: integer[]; + private optionCursors: number[]; private settingLabels: Phaser.GameObjects.Text[]; private optionValueLabels: Phaser.GameObjects.Text[][]; @@ -117,7 +119,7 @@ export default class AbstractSettingsUiHandler extends UiHandler { const labelWidth = Math.max(78, this.settingLabels[s].displayWidth + 8); - const totalSpace = (300 - labelWidth) - totalWidth / 6; + const totalSpace = (297 - labelWidth) - totalWidth / 6; const optionSpacing = Math.floor(totalSpace / (this.optionValueLabels[s].length - 1)); let xOffset = 0; @@ -130,7 +132,11 @@ export default class AbstractSettingsUiHandler extends UiHandler { this.optionCursors = this.settings.map(setting => setting.default); + this.scrollBar = new ScrollBar(this.scene, this.optionsBg.width - 9, this.optionsBg.y + 5, 4, this.optionsBg.height - 11, this.rowsToDisplay); + this.scrollBar.setTotalRows(this.settings.length); + this.settingsContainer.add(this.optionsBg); + this.settingsContainer.add(this.scrollBar); this.settingsContainer.add(this.navigationContainer); this.settingsContainer.add(actionsBg); this.settingsContainer.add(this.optionsContainer); @@ -186,6 +192,7 @@ export default class AbstractSettingsUiHandler extends UiHandler { this.settingsContainer.setVisible(true); this.setCursor(0); + this.setScrollCursor(0); this.getUi().moveTo(this.settingsContainer, this.getUi().length - 1); @@ -301,11 +308,12 @@ export default class AbstractSettingsUiHandler 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 { const ret = super.setCursor(cursor); if (!this.cursorObj) { - this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, (this.scene.game.canvas.width / 6) - 10, 16, 1, 1, 1, 1); + const cursorWidth = (this.scene.game.canvas.width / 6) - (this.scrollBar.visible? 16 : 10); + this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, cursorWidth, 16, 1, 1, 1, 1); this.cursorObj.setOrigin(0, 0); this.optionsContainer.add(this.cursorObj); } @@ -323,7 +331,7 @@ export default class AbstractSettingsUiHandler extends UiHandler { * @param save - Whether to save the setting to local storage. * @returns `true` if the option cursor was set successfully. */ - setOptionCursor(settingIndex: integer, cursor: integer, save?: boolean): boolean { + setOptionCursor(settingIndex: number, cursor: number, save?: boolean): boolean { const setting = this.settings[settingIndex]; if (setting.key === SettingKeys.Touch_Controls && cursor && hasTouchscreen() && isMobile()) { @@ -359,12 +367,13 @@ export default class AbstractSettingsUiHandler extends UiHandler { * @param scrollCursor - The scroll cursor position to set. * @returns `true` if the scroll cursor was set successfully. */ - setScrollCursor(scrollCursor: integer): boolean { + setScrollCursor(scrollCursor: number): boolean { if (scrollCursor === this.scrollCursor) { return false; } this.scrollCursor = scrollCursor; + this.scrollBar.setScrollCursor(this.scrollCursor); this.updateSettingsScroll(); @@ -394,6 +403,7 @@ export default class AbstractSettingsUiHandler extends UiHandler { clear() { super.clear(); this.settingsContainer.setVisible(false); + this.setScrollCursor(0); this.eraseCursor(); this.getUi().bgmBar.toggleBgmBar(this.scene.showBgmBar); if (this.reloadRequired) { diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 205df996529..06aa1f2e0d1 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -83,7 +83,7 @@ const languageSettings: { [key: string]: LanguageSetting } = { }, "fr":{ starterInfoTextSize: "54px", - instructionTextSize: "35px", + instructionTextSize: "38px", }, "it":{ starterInfoTextSize: "56px", @@ -627,7 +627,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const starterBoxContainer = this.scene.add.container(speciesContainerX + 6, 9); //115 - this.starterSelectScrollBar = new ScrollBar(this.scene, 161, 12, 0); + this.starterSelectScrollBar = new ScrollBar(this.scene, 161, 12, 5, starterContainerWindow.height - 6, 9); starterBoxContainer.add(this.starterSelectScrollBar); @@ -2544,8 +2544,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } }); - this.starterSelectScrollBar.setPages(Math.max(Math.ceil(this.filteredStarterContainers.length / 9), 1)); - this.starterSelectScrollBar.setPage(0); + this.starterSelectScrollBar.setTotalRows(Math.max(Math.ceil(this.filteredStarterContainers.length / 9), 1)); + this.starterSelectScrollBar.setScrollCursor(0); // sort const sort = this.filterBar.getVals(DropDownColumn.SORT)[0]; @@ -2580,7 +2580,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const onScreenFirstIndex = this.scrollCursor * maxColumns; const onScreenLastIndex = Math.min(this.filteredStarterContainers.length - 1, onScreenFirstIndex + maxRows * maxColumns -1); - this.starterSelectScrollBar.setPage(this.scrollCursor); + this.starterSelectScrollBar.setScrollCursor(this.scrollCursor); let pokerusCursorIndex = 0; this.filteredStarterContainers.forEach((container, i) => { diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index 67a4f7260e6..4087b397ff7 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -3,11 +3,14 @@ import OptionSelectUiHandler from "./settings/option-select-ui-handler"; import { Mode } from "./ui"; import * as Utils from "../utils"; import { TextStyle, addTextObject, getTextStyleOptions } from "./text"; -import { getBattleCountSplashMessage, getSplashMessages } from "../data/splash-messages"; +import { getSplashMessages } from "../data/splash-messages"; import i18next from "i18next"; import { TimedEventDisplay } from "#app/timed-event-manager"; export default class TitleUiHandler extends OptionSelectUiHandler { + /** If the stats can not be retrieved, use this fallback value */ + private static readonly BATTLES_WON_FALLBACK: number = -99999999; + private titleContainer: Phaser.GameObjects.Container; private playerCountLabel: Phaser.GameObjects.Text; private splashMessage: string; @@ -72,8 +75,8 @@ export default class TitleUiHandler extends OptionSelectUiHandler { .then(request => request.json()) .then(stats => { this.playerCountLabel.setText(`${stats.playerCount} ${i18next.t("menu:playersOnline")}`); - if (this.splashMessage === getBattleCountSplashMessage()) { - this.splashMessageText.setText(getBattleCountSplashMessage().replace("{COUNT}", stats.battleCount.toLocaleString("en-US"))); + if (this.splashMessage === "splashMessages:battlesWon") { + this.splashMessageText.setText(i18next.t(this.splashMessage, { count: stats.battleCount })); } }) .catch(err => { @@ -86,7 +89,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler { if (ret) { this.splashMessage = Utils.randItem(getSplashMessages()); - this.splashMessageText.setText(this.splashMessage.replace("{COUNT}", "?")); + this.splashMessageText.setText(i18next.t(this.splashMessage, { count: TitleUiHandler.BATTLES_WON_FALLBACK })); const ui = this.getUi(); diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 0f4fa52e41e..7e00c87cc5f 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -139,7 +139,8 @@ const noTransitionModes = [ Mode.TEST_DIALOGUE, Mode.AUTO_COMPLETE, Mode.ADMIN, - Mode.MYSTERY_ENCOUNTER + Mode.MYSTERY_ENCOUNTER, + Mode.RUN_INFO ]; export default class UI extends Phaser.GameObjects.Container { diff --git a/vitest.config.ts b/vitest.config.ts index 54462675704..9f9245687a1 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,6 +4,7 @@ import { defaultConfig } from "./vite.config"; export default defineProject(({ mode }) => ({ ...defaultConfig, test: { + testTimeout: 20000, setupFiles: ["./src/test/fontFace.setup.ts", "./src/test/vitest.setup.ts"], server: { deps: {