Merge branch 'beta' into healBlock

This commit is contained in:
Mumble 2024-09-20 12:03:28 -07:00 committed by GitHub
commit 43e1b3db85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
219 changed files with 5638 additions and 1632 deletions

View File

@ -55,8 +55,8 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
- Pokémon Sword/Shield
- Pokémon Legends: Arceus
- Pokémon Scarlet/Violet
- Firel (Custom Laboratory, Metropolis, Seabed, and Space biome music)
- Lmz (Custom Jungle biome music)
- Firel (Custom Ice Cave, Laboratory, Metropolis, Plains, Power Plant, Seabed, Space, and Volcano biome music)
- Lmz (Custom Ancient Ruins, Jungle, and Lake biome music)
- Andr06 (Custom Slum and Sea biome music)
### 🎵 Sound Effects

View File

@ -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,61 +6,101 @@ 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"];
// 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;
}
/**
* 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:`,
},
]);
if (!fileNameAnswer.userInput || fileNameAnswer.userInput.trim().length === 0) {
console.error("Please provide a valid file name!");
return await promptFileName(selectedType);
}
return fileNameAnswer;
}
/**
* Runs the interactive create-test "CLI"
* @returns {Promise<void>}
*/
async function runInteractive() {
const typeAnswer = await promptTestType();
const fileNameAnswer = await promptFileName(typeAnswer.selectedOption);
const type = typeAnswer.selectedOption.toLowerCase();
// 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
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
.toLowerCase(); // Ensure all lowercase
// Format the description for the test case
const formattedName = fileName
.replace(/_/g, ' ')
.replace(/\b\w/g, char => char.toUpperCase());
const formattedName = fileName.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
// Determine the directory based on the type
let dir;
let description;
if (type === 'move') {
dir = path.join(__dirname, 'src', 'test', 'moves');
switch (type) {
case "move":
dir = path.join(__dirname, "src", "test", "moves");
description = `Moves - ${formattedName}`;
} else if (type === 'ability') {
dir = path.join(__dirname, 'src', 'test', 'abilities');
break;
case "ability":
dir = path.join(__dirname, "src", "test", "abilities");
description = `Abilities - ${formattedName}`;
} else if (type === "item") {
dir = path.join(__dirname, 'src', 'test', 'items');
break;
case "item":
dir = path.join(__dirname, "src", "test", "items");
description = `Items - ${formattedName}`;
} else {
break;
default:
console.error('Invalid type. Please use "move", "ability", or "item".');
process.exit(1);
}
// Ensure the directory exists
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// 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);
}
// Define the content template
const content = `import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
@ -104,7 +140,23 @@ describe("${description}", () => {
});
`;
// Ensure the directory exists
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// 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');
fs.writeFileSync(filePath, content, "utf8");
console.log(`File created at: ${filePath}`);
}
runInteractive();

View File

@ -26,11 +26,37 @@ body {
#app {
display: flex;
justify-content: center;
align-items: center;
}
#app > div:first-child {
transform-origin: center !important;
}
/*
Supports automatic vertical centering as suggested in PR#1114, but only via CSS
Condition factorized to deduce CSS rules:
true if (isLandscape && !isMobile() && !hasTouchscreen() || (hasTouchscreen() && !isTouchControlsEnabled))
*/
/* isLandscape && !isMobile() && !hasTouchscreen() */
@media (orientation: landscape) and (pointer: fine) {
#app {
align-items: center;
}
}
@media (pointer: coarse) {
/* hasTouchscreen() && !isTouchControlsEnabled */
body:has(> #touchControls[class=visible]) #app {
align-items: start;
}
body:has(> #touchControls[class=visible]) #app > div:first-child {
transform-origin: top !important;
}
}
#layout:fullscreen #dpad, #layout:fullscreen {
bottom: 6rem;

453
package-lock.json generated
View File

@ -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"
}
}
}
}

View File

@ -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",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -27,7 +27,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 0,
@ -115,7 +115,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 0,
@ -215,7 +215,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 0,
@ -315,7 +315,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 0,
@ -414,7 +414,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 0,
@ -538,7 +538,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 23,
@ -685,7 +685,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": -19,
@ -784,7 +784,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 26,
@ -883,7 +883,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 23.5,
@ -994,7 +994,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 9,
@ -1069,7 +1069,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": -18.5,
@ -1157,7 +1157,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 37.5,
@ -1221,7 +1221,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 0,
@ -1284,7 +1284,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 0,
@ -1348,7 +1348,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 0,
@ -1448,7 +1448,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 0,
@ -1548,7 +1548,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 0,
@ -1647,7 +1647,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 0,
@ -1759,7 +1759,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": -25.5,
@ -1870,7 +1870,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 12,
@ -1957,7 +1957,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": -27,
@ -2044,7 +2044,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": -16,
@ -2143,7 +2143,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": -26.5,
@ -2230,7 +2230,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 23,
@ -2306,7 +2306,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": 24,
@ -2346,7 +2346,7 @@
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
"focus": 1
},
{
"x": -27,

Binary file not shown.

View File

@ -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$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,188 @@
{
"textures": [
{
"image": "statuses_ca_ES.png",
"format": "RGBA8888",
"size": {
"w": 22,
"h": 64
},
"scale": 1,
"frames": [
{
"filename": "pokerus",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 22,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
},
"frame": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
}
},
{
"filename": "burn",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 8,
"w": 20,
"h": 8
}
},
{
"filename": "faint",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 16,
"w": 20,
"h": 8
}
},
{
"filename": "freeze",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 24,
"w": 20,
"h": 8
}
},
{
"filename": "paralysis",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 32,
"w": 20,
"h": 8
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 40,
"w": 20,
"h": 8
}
},
{
"filename": "sleep",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 48,
"w": 20,
"h": 8
}
},
{
"filename": "toxic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 56,
"w": 20,
"h": 8
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

View File

@ -0,0 +1,188 @@
{
"textures": [
{
"image": "statuses_de.png",
"format": "RGBA8888",
"size": {
"w": 22,
"h": 64
},
"scale": 1,
"frames": [
{
"filename": "pokerus",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 22,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
},
"frame": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
}
},
{
"filename": "burn",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 8,
"w": 20,
"h": 8
}
},
{
"filename": "faint",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 16,
"w": 20,
"h": 8
}
},
{
"filename": "freeze",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 24,
"w": 20,
"h": 8
}
},
{
"filename": "paralysis",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 32,
"w": 20,
"h": 8
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 40,
"w": 20,
"h": 8
}
},
{
"filename": "sleep",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 48,
"w": 20,
"h": 8
}
},
{
"filename": "toxic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 56,
"w": 20,
"h": 8
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,188 @@
{
"textures": [
{
"image": "statuses_es.png",
"format": "RGBA8888",
"size": {
"w": 22,
"h": 64
},
"scale": 1,
"frames": [
{
"filename": "pokerus",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 22,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
},
"frame": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
}
},
{
"filename": "burn",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 8,
"w": 20,
"h": 8
}
},
{
"filename": "faint",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 16,
"w": 20,
"h": 8
}
},
{
"filename": "freeze",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 24,
"w": 20,
"h": 8
}
},
{
"filename": "paralysis",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 32,
"w": 20,
"h": 8
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 40,
"w": 20,
"h": 8
}
},
{
"filename": "sleep",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 48,
"w": 20,
"h": 8
}
},
{
"filename": "toxic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 56,
"w": 20,
"h": 8
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

View File

@ -0,0 +1,188 @@
{
"textures": [
{
"image": "statuses_fr.png",
"format": "RGBA8888",
"size": {
"w": 22,
"h": 64
},
"scale": 1,
"frames": [
{
"filename": "pokerus",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 22,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
},
"frame": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
}
},
{
"filename": "burn",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 8,
"w": 20,
"h": 8
}
},
{
"filename": "faint",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 16,
"w": 20,
"h": 8
}
},
{
"filename": "freeze",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 24,
"w": 20,
"h": 8
}
},
{
"filename": "paralysis",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 32,
"w": 20,
"h": 8
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 40,
"w": 20,
"h": 8
}
},
{
"filename": "sleep",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 48,
"w": 20,
"h": 8
}
},
{
"filename": "toxic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 56,
"w": 20,
"h": 8
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,188 @@
{
"textures": [
{
"image": "statuses_it.png",
"format": "RGBA8888",
"size": {
"w": 22,
"h": 64
},
"scale": 1,
"frames": [
{
"filename": "pokerus",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 22,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
},
"frame": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
}
},
{
"filename": "burn",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 8,
"w": 20,
"h": 8
}
},
{
"filename": "faint",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 16,
"w": 20,
"h": 8
}
},
{
"filename": "freeze",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 24,
"w": 20,
"h": 8
}
},
{
"filename": "paralysis",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 32,
"w": 20,
"h": 8
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 40,
"w": 20,
"h": 8
}
},
{
"filename": "sleep",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 48,
"w": 20,
"h": 8
}
},
{
"filename": "toxic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 56,
"w": 20,
"h": 8
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

View File

@ -0,0 +1,188 @@
{
"textures": [
{
"image": "statuses_ja.png",
"format": "RGBA8888",
"size": {
"w": 22,
"h": 64
},
"scale": 1,
"frames": [
{
"filename": "pokerus",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 22,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
},
"frame": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
}
},
{
"filename": "burn",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 8,
"w": 20,
"h": 8
}
},
{
"filename": "faint",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 16,
"w": 20,
"h": 8
}
},
{
"filename": "freeze",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 24,
"w": 20,
"h": 8
}
},
{
"filename": "paralysis",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 32,
"w": 20,
"h": 8
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 40,
"w": 20,
"h": 8
}
},
{
"filename": "sleep",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 48,
"w": 20,
"h": 8
}
},
{
"filename": "toxic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 56,
"w": 20,
"h": 8
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,188 @@
{
"textures": [
{
"image": "statuses_ko.png",
"format": "RGBA8888",
"size": {
"w": 22,
"h": 64
},
"scale": 1,
"frames": [
{
"filename": "pokerus",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 22,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
},
"frame": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
}
},
{
"filename": "burn",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 8,
"w": 20,
"h": 8
}
},
{
"filename": "faint",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 16,
"w": 20,
"h": 8
}
},
{
"filename": "freeze",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 24,
"w": 20,
"h": 8
}
},
{
"filename": "paralysis",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 32,
"w": 20,
"h": 8
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 40,
"w": 20,
"h": 8
}
},
{
"filename": "sleep",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 48,
"w": 20,
"h": 8
}
},
{
"filename": "toxic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 56,
"w": 20,
"h": 8
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

View File

@ -0,0 +1,188 @@
{
"textures": [
{
"image": "statuses_pt_BR.png",
"format": "RGBA8888",
"size": {
"w": 22,
"h": 64
},
"scale": 1,
"frames": [
{
"filename": "pokerus",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 22,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
},
"frame": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
}
},
{
"filename": "burn",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 8,
"w": 20,
"h": 8
}
},
{
"filename": "faint",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 16,
"w": 20,
"h": 8
}
},
{
"filename": "freeze",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 24,
"w": 20,
"h": 8
}
},
{
"filename": "paralysis",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 32,
"w": 20,
"h": 8
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 40,
"w": 20,
"h": 8
}
},
{
"filename": "sleep",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 48,
"w": 20,
"h": 8
}
},
{
"filename": "toxic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 56,
"w": 20,
"h": 8
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

View File

@ -0,0 +1,188 @@
{
"textures": [
{
"image": "statuses_zh_CN.png",
"format": "RGBA8888",
"size": {
"w": 22,
"h": 64
},
"scale": 1,
"frames": [
{
"filename": "pokerus",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 22,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
},
"frame": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
}
},
{
"filename": "burn",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 8,
"w": 20,
"h": 8
}
},
{
"filename": "faint",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 16,
"w": 20,
"h": 8
}
},
{
"filename": "freeze",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 24,
"w": 20,
"h": 8
}
},
{
"filename": "paralysis",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 32,
"w": 20,
"h": 8
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 40,
"w": 20,
"h": 8
}
},
{
"filename": "sleep",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 48,
"w": 20,
"h": 8
}
},
{
"filename": "toxic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 56,
"w": 20,
"h": 8
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

View File

@ -0,0 +1,188 @@
{
"textures": [
{
"image": "statuses.png",
"format": "RGBA8888",
"size": {
"w": 22,
"h": 64
},
"scale": 1,
"frames": [
{
"filename": "pokerus",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 22,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
},
"frame": {
"x": 0,
"y": 0,
"w": 22,
"h": 8
}
},
{
"filename": "burn",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 8,
"w": 20,
"h": 8
}
},
{
"filename": "faint",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 16,
"w": 20,
"h": 8
}
},
{
"filename": "freeze",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 24,
"w": 20,
"h": 8
}
},
{
"filename": "paralysis",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 32,
"w": 20,
"h": 8
}
},
{
"filename": "poison",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 40,
"w": 20,
"h": 8
}
},
{
"filename": "sleep",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 48,
"w": 20,
"h": 8
}
},
{
"filename": "toxic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 56,
"w": 20,
"h": 8
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

View File

@ -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$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

View File

@ -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 ?? {};
@ -2157,6 +2186,16 @@ export default class BattleScene extends SceneBase {
return 13.13;
case "battle_macro_boss": //SWSH Rose Battle
return 11.42;
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 +2642,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 +2653,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 {

View File

@ -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,11 +517,13 @@ 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)),
[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)),
[64]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35)
@ -515,17 +531,21 @@ export const classicFixedBattles: FixedBattleConfigs = {
[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)),
[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)),
[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 ])),
[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 ]))
.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 ]))
.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 })
};

View File

@ -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;

View File

@ -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<boolean> {
return new Promise<boolean>(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<boolean> {
return new Promise<boolean>(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 => {
@ -2625,7 +2625,11 @@ export class PreStatStageChangeAbAttr extends AbAttr {
}
}
/**
* Protect one or all {@linkcode BattleStat} from reductions caused by other Pokémon's moves and Abilities
*/
export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
/** {@linkcode BattleStat} to protect or `undefined` if **all** {@linkcode BattleStat} are protected */
private protectedStat?: BattleStat;
constructor(protectedStat?: BattleStat) {
@ -2634,7 +2638,17 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
this.protectedStat = protectedStat;
}
applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean {
/**
* Apply the {@linkcode ProtectedStatAbAttr} to an interaction
* @param _pokemon
* @param _passive
* @param simulated
* @param stat the {@linkcode BattleStat} being affected
* @param cancelled The {@linkcode Utils.BooleanHolder} that will be set to true if the stat is protected
* @param _args
* @returns true if the stat is protected, false otherwise
*/
applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean {
if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) {
cancelled.value = true;
return true;
@ -3757,7 +3771,7 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr {
this.multiplier = multiplier;
}
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
(args[0] as Utils.IntegerHolder).value *= this.multiplier;
return true;

View File

@ -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()) {
@ -488,14 +488,14 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
} else {
moveAnims.set(move, null);
const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP;
const moveName = Moves[move].toLowerCase().replace(/\_/g, "-");
const fetchAnimAndResolve = (move: Moves) => {
scene.cachedFetch(`./battle-anims/${moveName}.json`)
scene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`)
.then(response => {
const contentType = response.headers.get("content-type");
if (!response.ok || contentType?.indexOf("application/json") === -1) {
console.error(`Could not load animation file for move '${moveName}'`, response.status, response.statusText);
populateMoveAnim(move, moveAnims.get(defaultMoveAnim));
useDefaultAnim(move, defaultMoveAnim);
logMissingMoveAnim(move, response.status, response.statusText);
return resolve();
}
return response.json();
@ -515,6 +515,11 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
} else {
resolve();
}
})
.catch(error => {
useDefaultAnim(move, defaultMoveAnim);
logMissingMoveAnim(move, error);
return resolve();
});
};
fetchAnimAndResolve(move);
@ -522,6 +527,29 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
});
}
/**
* Populates the default animation for the given move.
*
* @param move the move to populate an animation for
* @param defaultMoveAnim the move to use as the default animation
*/
function useDefaultAnim(move: Moves, defaultMoveAnim: Moves) {
populateMoveAnim(move, moveAnims.get(defaultMoveAnim));
}
/**
* Helper method for printing a warning to the console when a move animation is missing.
*
* @param move the move to populate an animation for
* @param optionalParams parameters to add to the error logging
*
* @remarks use {@linkcode useDefaultAnim} to use a default animation
*/
function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) {
const moveName = Utils.animationFileName(move);
console.warn(`Could not load animation file for move '${moveName}'`, ...optionalParams);
}
/**
* Fetches animation configs to be used in a Mystery Encounter
* @param scene
@ -961,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);

View File

@ -1,6 +1,6 @@
import BattleScene from "../battle-scene";
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./pokemon-species";
import { VariantTier } from "../enums/variant-tiers";
import { VariantTier } from "../enums/variant-tier";
import * as Utils from "../utils";
import Overrides from "#app/overrides";
import { pokemonPrevolutions } from "./pokemon-evolutions";
@ -178,7 +178,7 @@ export class Egg {
// be done because species with no variants get filtered at rollSpecies but if the
// species is set via options or the legendary gacha pokemon gets choosen the check never happens
if (this._species && !getPokemonSpecies(this._species).hasVariants()) {
this._variantTier = VariantTier.COMMON;
this._variantTier = VariantTier.STANDARD;
}
// Needs this._tier so it needs to be generated afer the tier override if bought from same species
this._eggMoveIndex = eggOptions?.eggMoveIndex ?? this.rollEggMoveIndex();
@ -494,12 +494,12 @@ export class Egg {
// place but I don't want to touch the pokemon class.
private rollVariant(): VariantTier {
if (!this.isShiny) {
return VariantTier.COMMON;
return VariantTier.STANDARD;
}
const rand = Utils.randSeedInt(10);
if (rand >= 4) {
return VariantTier.COMMON; // 6/10
return VariantTier.STANDARD; // 6/10
} else if (rand >= 1) {
return VariantTier.RARE; // 3/10
} else {

View File

@ -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;
}
}
/**
@ -3839,7 +3847,7 @@ export class StormAccuracyAttr extends VariableAccuracyAttr {
* @extends VariableAccuracyAttr
* @see {@linkcode apply}
*/
export class MinimizeAccuracyAttr extends VariableAccuracyAttr {
export class AlwaysHitMinimizeAttr extends VariableAccuracyAttr {
/**
* @see {@linkcode apply}
* @param user N/A
@ -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,7 +4859,9 @@ 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 HitsTagAttr extends MoveAttr {
@ -4861,7 +4870,7 @@ export class HitsTagAttr extends MoveAttr {
/** 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 HitsTagAttr 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,12 +6771,11 @@ 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(HitsTagAttr, 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)
.attr(ForceSwitchOutAttr)
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
.ignoresSubstitute()
.hidesTarget()
.windMove(),
@ -6770,8 +6788,8 @@ export function initMoves() {
new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1),
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(MinimizeAccuracyAttr)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.attr(AlwaysHitMinimizeAttr)
.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,8 +6813,8 @@ export function initMoves() {
.attr(OneHitKOAccuracyAttr),
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(MinimizeAccuracyAttr)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.attr(AlwaysHitMinimizeAttr)
.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),
@ -6864,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(HitsTagAttr, 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),
@ -6947,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(HitsTagAttr, 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(HitsTagAttr, BattlerTagType.UNDERGROUND, true)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY ? 0.5 : 1)
.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(HitsTagAttr, 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,8 +7364,8 @@ export function initMoves() {
new AttackMove(Moves.MAGNITUDE, Type.GROUND, MoveCategory.PHYSICAL, -1, 100, 30, -1, 0, 2)
.attr(PreMoveMessageAttr, magnitudeMessageFunc)
.attr(MagnitudePowerAttr)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY ? 0.5 : 1)
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, true)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
.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)
@ -7403,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(HitsTagAttr, BattlerTagType.FLYING, true)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.FLYING)
.attr(FlinchAttr)
.windMove()
.target(MoveTarget.ALL_NEAR_ENEMIES),
@ -7435,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(HitsTagAttr, 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)
@ -7533,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)
@ -7889,8 +7907,8 @@ export function initMoves() {
new AttackMove(Moves.DRAGON_PULSE, Type.DRAGON, MoveCategory.SPECIAL, 85, 100, 10, -1, 0, 4)
.pulseMove(),
new AttackMove(Moves.DRAGON_RUSH, Type.DRAGON, MoveCategory.PHYSICAL, 100, 75, 10, 20, 0, 4)
.attr(MinimizeAccuracyAttr)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.attr(AlwaysHitMinimizeAttr)
.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)
@ -8087,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(HitsTagAttr, 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),
@ -8100,9 +8118,9 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
.danceMove(),
new AttackMove(Moves.HEAVY_SLAM, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
.attr(MinimizeAccuracyAttr)
.attr(AlwaysHitMinimizeAttr)
.attr(CompareWeightPowerAttr)
.attr(HitsTagAttr, 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)
@ -8185,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),
@ -8221,7 +8239,7 @@ export function initMoves() {
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.BULLDOZE, Type.GROUND, MoveCategory.PHYSICAL, 60, 100, 20, 100, 0, 5)
.attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY ? 0.5 : 1)
.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.FROST_BREATH, Type.ICE, MoveCategory.SPECIAL, 60, 90, 10, 100, 0, 5)
@ -8253,12 +8271,14 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.DEF ], -1)
.slicingMove(),
new AttackMove(Moves.HEAT_CRASH, Type.FIRE, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
.attr(MinimizeAccuracyAttr)
.attr(AlwaysHitMinimizeAttr)
.attr(CompareWeightPowerAttr)
.attr(HitsTagAttr, 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(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED)
.attr(FlinchAttr),
new SelfStatusMove(Moves.COTTON_GUARD, Type.GRASS, -1, 10, -1, 0, 5)
.attr(StatStageChangeAttr, [ Stat.DEF ], 3, true),
@ -8271,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(HitsTagAttr, 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)
@ -8325,9 +8345,9 @@ export function initMoves() {
.attr(LastMoveDoublePowerAttr, Moves.FUSION_FLARE)
.makesContact(false),
new AttackMove(Moves.FLYING_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, 0, 6)
.attr(MinimizeAccuracyAttr)
.attr(AlwaysHitMinimizeAttr)
.attr(FlyingTypeMultiplierAttr)
.attr(HitsTagAttr, 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)
@ -8498,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(HitsTagAttr, BattlerTagType.FLYING, false)
.attr(HitsTagAttr, 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)
@ -8756,6 +8776,8 @@ export function initMoves() {
.partial()
.ignoresVirtual(),
new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7)
.attr(AlwaysHitMinimizeAttr)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.partial()
.ignoresVirtual(),
new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7)
@ -9103,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)

View File

@ -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();

View File

@ -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);

View File

@ -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";

View File

@ -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,

View File

@ -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<void> {
return new Promise<void>(async resolve => {
const moveOptions = scene.currentBattle.mysteryEncounter!.misc.moveTutorOptions;

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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`,

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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));
}
}],
};

View File

@ -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

View File

@ -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;

View File

@ -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";

View File

@ -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,
});

View File

@ -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]);
}
}

View File

@ -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";

View File

@ -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.

View File

@ -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)

View File

@ -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];

View File

@ -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));

View File

@ -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`),
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`),
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);
}

View File

@ -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;

View File

@ -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]);
}

View File

@ -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 }));

View File

@ -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[];

View File

@ -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
};

View File

@ -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";

View File

@ -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() }));
}
}

View File

@ -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";

View File

@ -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<IMysteryEncounter> {
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<IMysteryEncounter> {
* 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<IMysteryEncounter, "options"> {
@ -732,7 +742,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
*
* @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<Pick<IMysteryEncounter, "requirements">> {
@ -798,7 +808,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* 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<Pick<IMysteryEncounter, "doEncounterRewards">> {
@ -812,7 +822,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* 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<Pick<IMysteryEncounter, "doEncounterExp">> {
@ -823,7 +833,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* 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<Pick<IMysteryEncounter, "onInit">> {
@ -833,7 +843,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
/**
* 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<Pick<IMysteryEncounter, "onVisualsStart">> {
@ -843,7 +853,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
/**
* 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<Pick<IMysteryEncounter, "catchAllowed">> {
@ -851,7 +861,16 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
}
/**
* @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<Pick<IMysteryEncounter, "fleeAllowed">> {
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<Pick<IMysteryEncounter, "hideBattleIntroMessage">> {
@ -859,7 +878,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
}
/**
* @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<Pick<IMysteryEncounter, "autoHideIntroVisuals">> {
@ -867,7 +886,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
}
/**
* @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<IMysteryEncounter> {
/**
* 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<IMysteryEncounter> {
/**
* 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<IMysteryEncounter> {
/**
* 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<IMysteryEncounter> {
/**
* Add outro dialogue/s for the encounter
*
* @param dialogue - outro dialogue/s
* @param dialogue Outro dialogue(s)
* @returns
*/
withOutroDialogue(dialogue: MysteryEncounterDialogue["outro"] = []): this {

View File

@ -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 * <number of missed spawns>) / 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<Biome, MysteryEncounterType[]>([
MysteryEncounterType.SAFARI_ZONE,
MysteryEncounterType.ABSOLUTE_AVARICE
]],
[Biome.SEA, [
MysteryEncounterType.LOST_AT_SEA
]],
@ -275,7 +276,9 @@ export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
[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 => {

View File

@ -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

View File

@ -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));
}
}
}

View File

@ -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<void> {
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);
}

View File

@ -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,

View File

@ -684,7 +684,7 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(Species.GROUDON, "", SpeciesFormKey.PRIMAL, new SpeciesFormChangeItemTrigger(FormChangeItem.RED_ORB))
],
[Species.RAYQUAZA]: [
new SpeciesFormChange(Species.RAYQUAZA, "", SpeciesFormKey.MEGA, new SpeciesFormChangeCompoundTrigger(new SpeciesFormChangeItemTrigger(FormChangeItem.RAYQUAZITE), new SpeciesFormChangeMoveLearnedTrigger(Moves.DRAGON_ASCENT)))
new SpeciesFormChange(Species.RAYQUAZA, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.RAYQUAZITE))
],
[Species.DEOXYS]: [
new SpeciesFormChange(Species.DEOXYS, "normal", "attack", new SpeciesFormChangeItemTrigger(FormChangeItem.SHARP_METEORITE)),

View File

@ -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}`);
}

View File

@ -1105,8 +1105,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);
@ -2302,6 +2310,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)
.setPartyTemplates(new TrainerPartyTemplate(3, PartyMemberStrength.STRONG))
};

View File

@ -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
}

View File

@ -107,6 +107,7 @@ export enum TrainerType {
VICKY,
VITO,
BUG_TYPE_SUPERFAN,
EXPERT_POKEMON_BREEDER,
BROCK = 200,
MISTY,

View File

@ -1,5 +0,0 @@
export enum VariantTier {
COMMON,
RARE,
EPIC
}

View File

@ -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";
@ -746,7 +746,7 @@ export class Arena {
case Biome.TOWN:
return 7.288;
case Biome.PLAINS:
return 7.693;
return 17.485;
case Biome.GRASS:
return 1.995;
case Biome.TALL_GRASS:
@ -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:
@ -774,13 +774,13 @@ export class Arena {
case Biome.DESERT:
return 1.143;
case Biome.ICE_CAVE:
return 15.010;
return 0.000;
case Biome.MEADOW:
return 3.891;
case Biome.POWER_PLANT:
return 2.810;
return 9.447;
case Biome.VOLCANO:
return 5.116;
return 17.637;
case Biome.GRAVEYARD:
return 3.232;
case Biome.DOJO:
@ -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:

View File

@ -95,10 +95,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public metLevel: integer;
public metBiome: Biome | -1;
public metSpecies: Species;
public metWave: number;
public luck: integer;
public pauseEvolutions: boolean;
public pokerus: boolean;
public wildFlee: boolean;
public switchOutStatus: boolean;
public evoCounter: integer;
public fusionSpecies: PokemonSpecies | null;
@ -144,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) {
@ -194,6 +195,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.luck = dataSource.luck;
this.metBiome = dataSource.metBiome;
this.metSpecies = dataSource.metSpecies ?? (this.metBiome !== -1 ? this.species.speciesId : this.species.getRootSpeciesId(true));
this.metWave = dataSource.metWave ?? (this.metBiome === -1 ? -1 : 0);
this.pauseEvolutions = dataSource.pauseEvolutions;
this.pokerus = !!dataSource.pokerus;
this.evoCounter = dataSource.evoCounter ?? 0;
@ -240,6 +242,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.metLevel = level;
this.metBiome = scene.currentBattle ? scene.arena.biomeType : -1;
this.metSpecies = species.speciesId;
this.metWave = scene.currentBattle ? scene.currentBattle.waveIndex : -1;
this.pokerus = false;
if (level > 1) {
@ -340,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 {
@ -1270,13 +1273,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param attrType {@linkcode AbAttr} The ability attribute to check for.
* @param canApply {@linkcode Boolean} If false, it doesn't check whether the ability is currently active
* @param ignoreOverride {@linkcode Boolean} If true, it ignores ability changing effects
* @returns {AbAttr[]} A list of all the ability attributes on this ability.
* @returns A list of all the ability attributes on this ability.
*/
getAbilityAttrs(attrType: { new(...args: any[]): AbAttr }, canApply: boolean = true, ignoreOverride?: boolean): AbAttr[] {
const abilityAttrs: AbAttr[] = [];
getAbilityAttrs<T extends AbAttr = AbAttr>(attrType: { new(...args: any[]): T }, canApply: boolean = true, ignoreOverride?: boolean): T[] {
const abilityAttrs: T[] = [];
if (!canApply || this.canApplyAbility()) {
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType));
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs<T>(attrType));
}
if (!canApply || this.canApplyAbility(true)) {
@ -2149,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<void> {
@ -2319,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:
@ -2392,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);
@ -2546,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;
@ -3355,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();
}
@ -3760,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);
}
@ -3783,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 {
@ -4081,6 +4131,7 @@ export class PlayerPokemon extends Pokemon {
newPokemon.metLevel = this.metLevel;
newPokemon.metBiome = this.metBiome;
newPokemon.metSpecies = this.metSpecies;
newPokemon.metWave = this.metWave;
newPokemon.fusionSpecies = this.fusionSpecies;
newPokemon.fusionFormIndex = this.fusionFormIndex;
newPokemon.fusionAbilityIndex = this.fusionAbilityIndex;
@ -4088,6 +4139,7 @@ export class PlayerPokemon extends Pokemon {
newPokemon.fusionVariant = this.fusionVariant;
newPokemon.fusionGender = this.fusionGender;
newPokemon.fusionLuck = this.fusionLuck;
newPokemon.usedTMs = this.usedTMs;
this.scene.getParty().push(newPokemon);
newPokemon.evolve((!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution)), evoSpecies);
@ -4779,6 +4831,7 @@ export class EnemyPokemon extends Pokemon {
this.pokeball = pokeballType;
this.metLevel = this.level;
this.metBiome = this.scene.arena.biomeType;
this.metWave = this.scene.currentBattle.waveIndex;
this.metSpecies = this.species.speciesId;
const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this);

View File

@ -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);
}
/**

View File

@ -241,12 +241,15 @@ export class LoadingScene extends SceneBase {
const lang = i18next.resolvedLanguage;
if (lang !== "en") {
if (Utils.verifyLang(lang)) {
this.loadAtlas(`statuses_${lang}`, "");
this.loadAtlas(`types_${lang}`, "");
} else {
// Fallback to English
this.loadAtlas("statuses", "");
this.loadAtlas("types", "");
}
} else {
this.loadAtlas("statuses", "");
this.loadAtlas("types", "");
}
const availableLangs = ["en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN"];

Some files were not shown because too many files have changed in this diff Show More